An integral aspect of content management is the ability to create new content. To create new content files with Tina, you will need to configure and register content-creator
plugins with the CMS.
Currently, Tina provides content-creator
plugins for both Markdown and JSON files in Gatsby projects. Once registered, actions from these content-creator
plugins are accessible from the sidebar menu. If you have an idea for a new content-creator
plugin, consider contributing!
content-creator
pluginThere are two content-creator
plugins to use with Gatsby.
RemarkCreatorPlugin
: Constructs a content-creator
plugin for Markdown files.interface RemarkCreatorPlugin{
label: string
fields: Field[]
filename(form: any): Promise<string>
frontmatter?(form: any): Promise<any>
body?(form: any): Promise<string>
}
JsonCreatorPlugin
: contstructs a content-creator
plugin for JSON files.interface JsonCreatorPlugin {
label: string
fields: Field[]
filename(form: any): Promise<string>
data?(form: any): Promise<any>
}
These classes need to be instantiated with at least these three things:
label
: A simple action label displayed when users interact with the + button in the sidebar.filename
: A function whose return value should be the path to the new file.fields
: An array of field objects. Read more on field definitions.Markdown Example
import { RemarkCreatorPlugin } from 'gatsby-tinacms-remark'
const CreatePostPlugin = new RemarkCreatorPlugin({
label: 'New Blog Post',
filename: form => {
return form.filename
},
fields: [
{
name: 'filename',
component: 'text',
label: 'Filename',
placeholder: 'content/blog/hello-world/index.md',
description:
'The full path to the new Markdown file, relative to the repository root.',
},
],
})
The
createRemarkButton
function is deprecated as ofgatsby-tinacms-remark: 0.4.0
. This is a function that served the same purpose as theRemarkCreatorPlugin
class. Below is an example ofcreateRemarkButton
in use.
import { createRemarkButton } from 'gatsby-tinacms-remark'
/*
** Deprecated — gatsby-tinacms-remark: 0.4.0
** in favor of RemarkCreatorPlugin class
*/
const CreatePostPlugin = createRemarkButton({
label: 'Create Post',
filename: form => {
return form.filename
},
fields: [
//...
],
})
JSON Example
import { JsonCreatorPlugin } from 'gatsby-tinacms-json'
const CreatePostPlugin = new JsonCreatorPlugin({
label: 'New JSON File',
filename: form => {
return form.filename
},
fields: [
{
name: 'filename',
component: 'text',
label: 'Filename',
placeholder: 'content/data/puppies.json',
description:
'The full path to the new Markdown file, relative to the repository root.',
},
],
})
When adding a content-creator
plugin, you'll have to consider when you want this functionality available to the editor. If the component where you registered the plugin is actively rendered on the site, you will be able to add new content via the plugin.
These are some places you may want to add the plugin:
Now that we've created the content-creator
plugin, we need to add it to the sidebar so we can access it. When we register the plugin to the sidebar, a create-icon will show up in the sidebar menu. Keep in mind this icon will only show up when the component that registers it is rendered.
In this example, we will add the button to the Tina sidebar when visiting the blog index page. There are 3 steps involved:
tinacms
RemarkCreatorPlugin
and withPlugin
content-creator
pluginFirst, if you haven't already, install the tinacms
package.
$ yarn add tinacms || npm install --save tinacms
Example: src/pages/index.js
// 1. Import `RemarkCreatorPlugin` and `withPlugin`
import { withPlugin } from 'tinacms'
import { RemarkCreatorPlugin } from 'gatsby-tinacms-remark'
// Note: this is just an example index component.
function BlogIndex(props) {
const { data } = props
const posts = data.allMarkdownRemark.edges
return (
<Layout location={props.location}>
{posts.map(({ node }) => {
const title = node.frontmatter.title || node.fields.slug
return (
<div key={node.fields.slug}>
<h3>
<Link style={{ boxShadow: `none` }} to={node.fields.slug}>
{title}
</Link>
</h3>
<small>{node.frontmatter.date}</small>
<p
dangerouslySetInnerHTML={{
__html: node.frontmatter.description || node.excerpt,
}}
/>
</div>
)
})}
</Layout>
)
}
// 2. Create the `content-creator` plugin
const CreatePostPlugin = new RemarkCreatorPlugin({
label: 'Create Post',
fields: [
{
name: 'filename',
component: 'text',
label: 'Filename',
placeholder: 'content/blog/hello-world/index.md',
description:
'The full path to the new markdown file, relative to the repository root.',
},
],
filename: form => {
return form.filename
},
})
// 3. Add the plugin to the component
export default withPlugin(BlogIndex, CreatePostPlugin)
NOTE: No changes need to be made to the
BlogIndex
component itself.
With the plugin in place, open TinaCMS and click the menu button in the top-left corner. The menu panel will slide into view with the button at the top.
Click the "Create Post" button and a modal will pop up. Enter the path to a new file relative to your repository root (e.g. content/blog/my-new-post.md
) and then click "create". A moment later the new post will be added to your Blog Index.
RemarkCreatorPlugin
accepts a fields
option, similar to Remark Form. When using a custom create form, all callback functions will receive an object containing all form data.
Example: Create Posts in Subdirectories
const CreatePostPlugin = new RemarkCreatorPlugin({
label: 'Create Post',
fields: [
{ name: 'section', label: 'Section', component: 'text', required: true },
{ name: 'title', label: 'Title', component: 'text', required: true },
],
filename: form => {
return `content/blog/${form.section}/${form.title}/index.md`
},
})
The RemarkCreatorPlugin
must be given a filename
function that calculates the path of the new file from the form data.
Example 1: Hardcoded Content Directory
const CreatePostPlugin = new RemarkCreatorPlugin({
label: 'Create Post',
fields: [
{ name: 'title', label: 'Title', component: 'text', required: true },
],
filename: form => `content/blog/${form.title}.md`,
})
Example 2: Content as index files
const CreatePostPlugin = new RemarkCreatorPlugin({
label: 'Create Post',
fields: [
{ name: 'title', label: 'Title', component: 'text', required: true },
],
filename: form => `content/blog/${form.title}/index.md`,
})
Example 3: Slugify Name
The
slugify
package is also great for this usecase.
const CreatePostPlugin = new RemarkCreatorPlugin({
label: 'Create Post',
fields: [
{ name: 'title', label: 'Title', component: 'text', required: true },
],
filename: form => {
let slug = form.title.replace(/\s+/, '-').toLowerCase()
return `content/blog/${slug}/index.md`
},
})
The RemarkCreatorPlugin
function can be given a frontmatter
function that returns the default front matter. Like the filename
function, frontmatter
receives the state of the form.
Example: Title + Date
const CreatePostPlugin = new RemarkCreatorPlugin({
label: 'Create Post',
fields: [
{ name: 'title', label: 'Title', component: 'text', required: true },
],
filename: form => {
let slug = form.title.replace(/\s+/, '-').toLowerCase()
return `content/blog/${slug}/index.md`
},
frontmatter: form => ({
title: form.title,
date: new Date(),
}),
})
The RemarkCreatorPlugin
function can be given a body
function that returns the default Markdown body. Like the previous two functions, body
receives the state of the form.
Example: Title + Date
const CreatePostPlugin = new RemarkCreatorPlugin({
label: 'Create Post',
fields: [
{ name: 'title', label: 'Title', component: 'text', required: true },
],
filename: form => {
let slug = form.title.replace(/\s+/, '-').toLowerCase()
return `content/blog/${slug}/index.md`
},
body: form => `This is a new blog post. Please write some content.`,
})
When you need to provide the option of deleting files in a Gatsby site, the config is much simpler. Instead of adding a creator
plugin to the sidebar, all we need to do is pass a delete action
to the form options object and the 'action' will be added to the sidebar.
Head to the template file where you may have a Tina form setup. Read more on setting up forms for content editing with remark or JSON. First, you'll need to import the DeleteAction
from gatsby-tinacms-remark
. Then, in your form options object just before the field definitions, add the action.
import { remarkForm, DeleteAction } from 'gatsby-tinacms-remark'
const BlogTemplateOptions = {
actions: [DeleteAction],
fields: [
//...
],
}
These form options then get passed to the RemarkForm
higher-order component, as seen in this example. With the DeleteAction
now passed to the form, you'll be able to access it via the three-dot icon in the lower right-hand corner of the sidebar.