diff --git a/packages/netlify-cms-core/src/backend.js b/packages/netlify-cms-core/src/backend.js index a2906785..16fb9353 100644 --- a/packages/netlify-cms-core/src/backend.js +++ b/packages/netlify-cms-core/src/backend.js @@ -42,7 +42,9 @@ const slugFormatter = (collection, entryData, slugConfig) => { const identifier = entryData.get(selectIdentifier(collection)); if (!identifier) { - throw new Error('Collection must have a field name that is a valid entry identifier'); + throw new Error( + 'Collection must have a field name that is a valid entry identifier, or must have `identifier_field` set', + ); } const slug = template diff --git a/packages/netlify-cms-core/src/constants/configSchema.js b/packages/netlify-cms-core/src/constants/configSchema.js index 51b6367a..fe1fef25 100644 --- a/packages/netlify-cms-core/src/constants/configSchema.js +++ b/packages/netlify-cms-core/src/constants/configSchema.js @@ -126,7 +126,7 @@ const getConfigSchema = () => ({ fields: { contains: { properties: { - name: { enum: IDENTIFIER_FIELDS }, + name: { enum: [{ $data: '3/identifier_field' }, ...IDENTIFIER_FIELDS] }, }, }, }, @@ -169,7 +169,7 @@ class ConfigError extends Error { * the config that is passed in. */ export function validateConfig(config) { - const ajv = new AJV({ allErrors: true, jsonPointers: true }); + const ajv = new AJV({ allErrors: true, jsonPointers: true, $data: true }); ajvErrors(ajv); const valid = ajv.validate(getConfigSchema(), config); diff --git a/packages/netlify-cms-core/src/reducers/collections.js b/packages/netlify-cms-core/src/reducers/collections.js index c873729f..e49621de 100644 --- a/packages/netlify-cms-core/src/reducers/collections.js +++ b/packages/netlify-cms-core/src/reducers/collections.js @@ -113,9 +113,10 @@ export const selectAllowDeletion = collection => export const selectTemplateName = (collection, slug) => selectors[collection.get('type')].templateName(collection, slug); export const selectIdentifier = collection => { + const identifier = collection.get('identifier_field'); + const indentifierFields = identifier ? [identifier, ...IDENTIFIER_FIELDS] : IDENTIFIER_FIELDS; const fieldNames = collection.get('fields').map(field => field.get('name')); - return IDENTIFIER_FIELDS.find(id => fieldNames.find(name => name.toLowerCase().trim() === id)); - // There must be a field whose `name` matches one of the IDENTIFIER_FIELDS. + return indentifierFields.find(id => fieldNames.find(name => name.toLowerCase().trim() === id)); }; export const selectInferedField = (collection, fieldName) => { const inferableField = INFERABLE_FIELDS[fieldName]; diff --git a/website/content/docs/collection-types.md b/website/content/docs/collection-types.md index a6d98de0..1cd650fe 100644 --- a/website/content/docs/collection-types.md +++ b/website/content/docs/collection-types.md @@ -13,7 +13,7 @@ Folder collections represent one or more files with the same format, fields, and Unlike file collections, folder collections have the option to allow editors to create new items in the collection. This is set by the boolean `create` field. -**Note:** Folder collections must have at least one field with the name `title` for creating new entry slugs. That field should use the default `string` widget. The `label` for the field can be any string value. See the [Collections reference doc](../configuration-options/#collections) for details on how collections and fields are configured. If you forget to add this field, you will get an error that your collection "must have a field that is a valid entry identifier". +**Note:** Folder collections must have at least one field with the name `title` for creating new entry slugs. That field should use the default `string` widget. The `label` for the field can be any string value. If you wish to use a different field as your identifier, set `identifier_field` to the field name. See the [Collections reference doc](../configuration-options/#collections) for details on how collections and fields are configured. If you forget to add this field, you will get an error that your collection "must have a field that is a valid entry identifier". Example: @@ -30,6 +30,21 @@ collections: - {label: "Body", name: "body", widget: "markdown"} ``` +With `identifier_field`: + +```yaml +- label: "Blog" + name: "blog" + folder: "_posts/blog" + create: true + identifier_field: name + fields: + - {label: "Name", name: "name", widget: "string"} + - {label: "Publish Date", name: "date", widget: "datetime"} + - {label: "Featured Image", name: "thumbnail", widget: "image"} + - {label: "Body", name: "body", widget: "markdown"} +``` + ### Filtered folder collections The entries for any folder collection can be filtered based on the value of a single field. By filtering a folder into different collections, you can manage files with different fields, options, extensions, etc. in the same folder.