Strapi v4 UID field generates weird slug

I’m trying to create a slug for a collection by using the UID field, and binding it to the name of the product so that “My Product 3K4AB” becomes my-product-3k4ab as a slug.

The problem is that it creates the slug my-product-3-k4-ab, so basically because the slugify module has set decamelize = true, it splits up the model number of the product.

What’s the best way to make it so that decamelize = false, but only for UIDs (and not for anywhere else Strapi uses the slugify module)?

I’ve figured it out. If you only have 1 collection you want the slug to be like this, you can just add

"slug":{ "type": "uid", "targetField": "name", "required": true, "options": { "decamelize": false } }

to the schema.json. Or if you want to have it apply to all future UID fields you create across all collections (who are binded to a text field called “name”), you have to make a file at src/extensions/content-manager/strapi-server.js and paste this in. If anyone finds something I did wrong please let me know.

//replaces the node_modules/@strapi/plugin-content-manager/server/services/uid.js 
//with this that automatically includes decamelization

const _ = require('lodash');
const slugify = require('@sindresorhus/slugify');

module.exports = (plugin) => {

    plugin.services.uid = ({ strapi }) => ({
        async generateUIDField({ contentTypeUID, field, data }) {
            const contentType = strapi.contentTypes[contentTypeUID];
            const { attributes } = contentType;

            let { targetField, default: defaultValue, options } = attributes[field];
            const targetValue = _.get(data, targetField);

            if (targetField === 'name') {
                options = {
                    decamelize: false,
                    ...options
                };
            }

            if (!_.isEmpty(targetValue)) {
                return this.findUniqueUID({
                    contentTypeUID,
                    field,
                    value: slugify(targetValue, options),
                });
            }

            return this.findUniqueUID({
                contentTypeUID,
                field,
                value: slugify(defaultValue || contentType.modelName, options),
            });
        },

        async findUniqueUID({ contentTypeUID, field, value }) {
            const query = strapi.db.query(contentTypeUID);

            const possibleColisions = await query
                .findMany({
                    where: { [field]: { $contains: value } },
                })
                .then(results => results.map(result => result[field]));

            if (possibleColisions.length === 0) {
                return value;
            }

            let i = 1;
            let tmpUId = `${value}-${i}`;
            while (possibleColisions.includes(tmpUId)) {
                i += 1;
                tmpUId = `${value}-${i}`;
            }

            return tmpUId;
        },

        async checkUIDAvailability({ contentTypeUID, field, value }) {
            const query = strapi.db.query(contentTypeUID);

            const count = await query.count({
                where: { [field]: value },
            });

            if (count > 0) return false;
            return true;
        },
    })
    return plugin;
};
2 Likes