How Keystatic organises your content
Keystatic has two concepts or structures to organise data: collections and singletons.
Those are defined in the Keystatic configuration.
You get a lot of control and flexibility with where your content gets generated, both at the collection or singleton level, and at the field level for certain field types, like images.
Path configuration
You can define where Keystatic should store collection entries and singletons via the path property in the collection/singleton top-level options:
// Keystatic config
export default config({
collections: {
posts: collection({
label: 'Posts',
path: 'content/posts/*/',
// ...
})
},
singletons: {
settings: singleton({
label: 'Settings',
path: 'content/posts/',
// ...
})
}
})
The optional trailing slash / on that path has an impact on the content structure - read below for more details on collection paths and singleton paths.
collections require a * wildcard part of the path string.
This will be replaced by the slug of the entry.
We will go over core concepts here, but check out the Path wildcard page for more details and advanced examples.
Collections
The default path value, if not specified, will be {collection-name}/*/.
Collection paths ending with a trailing slash /
If the path ends with a trailing slash /, each entry will be created in its own directory named after the slug:
collection-name
└── slug
├── index.yaml
└── other.mdocSay you create two entries in the posts collection, where the path is set to 'content/posts/*/'.
Since there is a trailing slash in the path, the generated output will look like so:
content
└── posts
├── my-first-post
├── index.yaml
├── other.mdoc
└── my-second-post
├── index.yaml
└── other.mdocCollection paths ending without a trailing slash
If the path does not end with a trailing slash, entries' index files will be created immediately inside the collection directory:
collection-name
├── slug.yaml
└── slug
└── other.mdocSay you create two entries in the posts collection, where the path is set to 'content/posts/*'.
Since there is no trailing slash in the path, the generated output will look like so:
content
└── posts
├── my-first-post.yaml
└── my-first-post
└── other.mdoc
├── my-second-post.yaml
└── my-second-post
└── other.mdocSingletons
The path property for singletons does not contain a * wildcard.
If not specified, the default path value for singletons will be {singleton-name}/.
Singleton paths ending with a trailing slash /
If the path ends with a trailing slash /, the singleton's content will be created inside a directory named after the singleton:
singleton-name
├── index.yaml
└── other.mdoc
Singleton paths ending without a trailing slash
If the path does not end with a trailing slash, the content will be stored in a file named after the singleton.
Additional document fields will be stored inside a directory with the same name:
singleton-name.yaml
singleton-name
└── other.mdoc
Images output path
You can decide where to store your images independently of the path configuration for a given collection or singleton.
This is useful when you want to have your images in the public or assets directory, to comply to framework-specific conventions.
// In the context of a `posts` collection...
coverImage: fields.image({
label: "Cover Image",
directory: "public/images/posts",
}),
Regardless of where the posts entries are created, the coverImage image will be generated in public/images/posts/{post-slug}.
Path prefix
If you're in a monorepo, you can use the storage.pathPrefix option to scope Keystatic to a specific directory instead of adding a prefix to every path option.
For example, this config will look for posts in somewhere/my-site/content/posts:
export default config({
storage: {
kind: 'github',
repo: 'my-org/my-repo',
pathPrefix: 'somewhere/my-site'
},
collections: {
posts: collection({
label: 'Posts',
path: 'content/posts/*/',
// ...
})
},
})Screencast walk-through
This segment of the Keystatic Mini-Course on YouTube also provides context on how the path configuration works: