Auto-fill created_by field

Hi, new to strapi and trying to get a “feel” for the framework before deciding whether to go for it or not.

I am using the “Users & permissions” plugin to allow users to login to my app and call various endpoints. The problem is that the created_by and updated_by fields are not automatically populated with the authenticated (via JWT through Autherntication header) user id

I know that when i created something from the actual CMS (/admin) created_by and updated_by will be populated by the CMS user, but i also want to have these fields, preferably automatically, populated by e.g. ctx.state.user.id for end-users that create entities

Is there any way to configure this, e.g. pass something to strapi.services.entity.create() that would also set created_by and updated_by respectively - or am i forced to do this for every route that wants this behavior? I know i can manually add e.g. a “created_by” relation to the users table, but i’d rather reuse created_by/updated_by to avoid confusion and added complexity

Thanks

Of course, right after posting this i found a post touching on the same subject. https://github.com/strapi/strapi/discussions/6871

I see the comment made by giacomocerquone 10 days ago, but i would like to avoid having to do manual labor in my controllers unless i really have to. I’d prefer to always have the state be passed to the hook, or have some other kind of middleware that allows me to set these fields myself. As long as i can stay DRY i’m happy.

@linde12 thank you for posting on the forum, we are planning to get rid of GH discussions in early 2021 so at this point I’m going to “migrate” the content of this discussion to this thread.

1 Like

Content migrated from: https://github.com/strapi/strapi/discussions/6871


Original Post

eporroa

I’ve a requirement to store the user id from multiple content types and was looking for a way to use lifecycle hooks: afterCreate , afterUpdate , afterDelete .
Looks like the lifecycle hook arguments only returns data from the specific model (entity and request params) but is there a way to access ctx.state.user from these functions?

I’ll appreciate all feedback! … Thanks!


Comments - Thread 1

derrickmehaffy

Collaborator

@alexandrebodin is ctx no longer passed to lifecycle callbacks? (I know it was previously before the changes made in stable).

If not, was there a specific reason we aren’t passing the full ctx over, or is it just not documented?

eporroa

Author

I even tried this:

    async afterCreate(result, data) {
      strapi.app.use(async ctx => {
        ctx; // is the Context
        console.log("ctx.request", ctx.request);
        console.log("ctx.response", ctx.response);
      });
      console.log(strapi.services.history.ACTIONS.CREATED);
      console.log('afterCreate', result, data);
    },

With no luck, is there any way to get the ctx object inside a hook?

alexandrebodin

Maintainer

Hi, the ctx is on the HTTP JSON API layer it is not possible to pass it down to the DB lifecycle layer automatically. The db layer can be called from anywhere and is not related to any current query.

What you can do is extend your controllers to update the input data with the ctx.state.user if you wish to.

FYI @derrickmehaffy it wasn’t in the previous lifecycles we just changed :slight_smile:

derrickmehaffy

Collaborator

I’m thinking we should at least pass down the state though like ctx.state.user as this is going to be a fairly common usecase I think (considering that lifecycles are called not only via the generated routes but also the admin) assuming that the admin also has a ctx.state.user which I have never really looked at.

derrickmehaffy

Collaborator

Looking through the old docs from beta.19.5 I guess the other options were never really clearly defined as to what they did beforeCreate: async (model, attrs, options)

alexandrebodin

Maintainer

DB lifecycles will not get request context. this should be handled by a layer an top of those lifecycles like a request lifecycle that will set those informations before calling the DB layer.

derrickmehaffy

Collaborator

DB lifecycles will not get request context. this should be handled by a layer an top of those lifecycles like a request lifecycle that will set those informations before calling the DB layer.

Are you hinting towards controller lifecycles the main reason I suppose why I try to avoid directing people to controller modification is for updates. Though not really a larger problem now as it was in the Alpha/Beta days where those would change often. Old habits die hard I suppose.


Comments - Thread 2

darron1217

Contributor

Is there any update on this issue?
For example, I want to send an email after User is created.
But the request should return error when sending email has failed.

derrickmehaffy

Collaborator

We do not have an update currently

darron1217

Contributor

I solved it by creating custom controller method.
Thank you anyway


Comments - Thread 3

giacomocerquone

This is a “dupe” of How to get context or Current user in Function beforeSave · Issue #3309 · strapi/strapi · GitHub

Honestly, with strapi many times you find yourself reimplementing controllers with few lines of code and therefore I don’t see any problem with this issue in particular honestly.
Just replace your create and then call strapi.services.model.create({ userObj }) passing in whatever object you want to have available in your lifecycle hook.

A slightly extended snippet:

module.exports = {
  async create(ctx) {
    const body = ctx.request.body;
    const user = ctx.state.user;

    if (!user) {
      return ctx.badRequest(null, [
        { messages: [{ id: "No authorization header was found" }] },
      ]);
    }

    const entity = await strapi.services.note.create({
      description: body.description,
      user: user,
    });

    return sanitizeEntity(entity, { model: strapi.models.note });
  },
};

and then in the model


module.exports = {
  lifecycles: {
    async beforeCreate(data) {
      console.log(data.userObj);
    }
  },
};

linde12

Just encountered a similar problem (where i want to auto-fill created_by and updated_by with end-user ids via the “Users & permissions” plugin)

I guess this is the best solution, but with this i can’t stay DRY, instead i have to implement each controller (even if they’re just plain CRUD controllers) and always pass ctx.state.user manually.

Maybe instead of using a global strapi.services.note.create we would have to have some sort of services-object which contains functions that are bound to the ctx.state passed to the actual controller, e.g.

async create(ctx, services) {
  services.note.create(ctx.request.body)
  ...
}

Maybe some sort of controller middleware could do this by leveraging the Proxy API, so that when calling services.note.create the state would be passed automatically as a second parameter to strapi.services.note.create

I realize that this is quite the large change, and there are probably better ways to do it, but currently it seems very inflexible :frowning:

derrickmehaffy

Collaborator

@alexandrebodin @Convly this was what I mentioned as well regarding some of the user feedback I’ve seen in slack. When we added RBAC we assume that all content is created in the admin, but in many users cases it could be coming from the user APIs.

Everyone else, I’ve created an actual feature request here: #8193
This is a topic we have discussed internally as well and quite a few of us internally agree about the topic. (Passing ctx into the lifecycles)

Related image of a sample we are planning in the new request layer I think will fit the role of what you guys are looking for, just note this isn’t set in stone:

image

1 Like

Sorry to post this here, but the question arises and maybe you have the solution (I’m new to Strapi). This is the reason why when using POST (with a new admin user) and later GET to consult the item created, those fields (created_by, updated_by) always appear in null? Could you tell me what the solution could be to obtain the user who created items?. Thanks.

Those fields are only populated if the entry was created in the Admin panel (it’s a relation to the strapi_administrators model that comes from the strapi-admin package). For REST/GraphQL you’ll need to add your own fields, and likewise probably customize the controller (or create a route policy) to populate them.

1 Like

I am trying to find an easy way to automatically set the author of an article and I am finding this very difficult. All the solutions for editing customer controllers only work when a user is consuming the API, not using the admin panel. When a user creates a new article through the admin panel, how can I automatically set the author field? if you can give a demonstration of the files requiring editing and the code required I would really appreciate it. I am somewhat new. Thank you.

That’s because there are two different authentication plugins

  • strapi-plugin-users-permissions => This is for end users, aka users using REST/GraphQL
  • strapi-admin => This is for the administration panel

The latter option is automatically handled for you if the content is created while in the admin panel, for the former I’d suggest using lifecycle callbacks or policies to set the author field (policies would be best as you can intercept the request before the controller is ran, using the ctx.state.user.id would allow you to set an author field)

You won’t have one field that can do both (technically you could using polymorphic relations, but their support is quite limited right now and I wouldn’t suggest it).

apparently, it’s not the case for me. no author or createdor fields are shown in the API response I tried with populate=*

is it changed after v4?

Hello. Right now I am just getting to know Strapi. I installed version 4.1.3, and here the created_by field is still not specified when retrieving collection data. You said that in version 4 this will be implemented, but so far it is not. Or am I doing something wrong and did I need to include this parameter somewhere?

Thank you for this, I am finding extending Strapi quite confusing at the moment - are there any examples of lifecycle callbacks and policies in V4?

I was just looking through the api return and sadly I cannot display the created by field. In the administrator panel, the created by field display the creator but only if created by an author or editior - not admin. Even doing so, the created by field is still not being displayed in the return json. I ended up creating a custom field that the author can enter their name. The upside to doing it this way is that the editor or admin can edit the author later if needed. The downside is that the author is needlessly being duplicated and is additional overhead in the database.

I hear you, have had to do the same in my project. Seems if you want to update data via the API it’s necessary to duplicate a lot of the good functionality that the admin panel already includes. It’s not gonna stress the DB much, just untidy.

I tag the logged in users ID against the object via some custom controller logic which you might find helpful…

// src/api/[apiname]/controllers/apiname.js
module.exports = createCoreController("api::retreat.retreat", ({ strapi }) => ({
  async create(ctx) {
    // assign the current user as the owner of the retreat
    Object.assign(ctx.request.body.data, { owner: ctx.state.user.id });

    const response = await super.create(ctx);

    return response;
  },
}));

Doesn’t make sense to me that the users are split and I need to re-create all the Author / Editor functionality… we can’t give our users the admin panel as they are very non-technical.

Yea - I’m mainly just using Strapi as a public feed right now so I haven’t needed to update via API yet. I’ll get there soon I imagine though.

Hey - thanks knowncolor! I actually really needed this for another app I’m working on!

1 Like

Hi @DMehaffy, I am also trying to populate the created_by and updated_by field from API in strapi but it is not getting populated.

Code : Getting users from /api/users
const getUserDetails = async () => {
await fetch(/api/users, {
method: “GET”
}).then(res => res.json()).then(data => {
if(data.length > 0) {
setUser(data);
} else {
setUser(‘’);
}
});
};

Can these field of created by and updated by be populated from the /api/users collection ? As I was trying to get the users from the “Users” collection and trying to populate the above fields but its not populating any value giving null. Can you please help me on how I can populate the details using API. Any help will be appreciated. Thanks