Deleting users from the OJS database

Hi @j1shin,

If you want to get rid of a test account, merge it into your administrator account. The distinction between “merge” and “delete” is that the merged account’s submissions etc. are preserved.

Alec Smecher
Public Knowledge Project Team

I too would love a way to just check a checkbox(es) and either delete or merge users automatically through the UI.

In the mean time, as a novice bash user, this worked for me.

SSH into your hosting environment/bash, cd into your OJS directory, and create a space-separated array of usernames and hit Enter/Return.

users=(username1 username2 username3 username4)

Assuming you’re in the main OJS directory, then type this:
for i in "${users[@]}"; do php tools/mergeUsers.php username_to_merge_into_here $i; done

This will loop through your array and merge each user in the “users” array into the “username_to_merge_into_here” username. Obviously replace that with the username you want to merge into. Also be sure that the mergeUsers.php has execute permissions for your user account.


Spam users not registered have not a role, they are not in the table roles. The “good” user have role and theirs user_id are present in the roles table.

The query to delete them is:

delete from users where user_id not in (select user_id from roles);

That sql sentence works in my case.

1 Like

Be aware that this will leave dangling foreign key references in the database, for example in the sessions table, the user_settings table, etc.

Thank you ctgraham. I suspected that, but is there any other way to delete 22.000 false users? It seems incredible but OJS have no way to delete these spam users. The recommendation to merge users one by one to delete them afterwards is not very exciting.
One wonders how is it possible that OJS allows the existence of these users in the first place.

You can combine your SQL query with the mergeUsers tool to remove the users in batch. One example is given here:

If new spam registrations are ongoing, there are several plugins which can help:

Hi all,

I have a github issue open here: Permit the "merge users" CLI tool to handle multiple users at once · Issue #4772 · pkp/pkp-lib · GitHub

It adds support for merging multiple users at once to the command-line tool, removing the need for a shell script loop. It also adds support for specifying users to be merged by user ID, not just username.

Alec Smecher
Public Knowledge Project Team

1 Like

Thank you so much to ctgraham and asmecher. Very useful information!!

this could be is usefull to retrieve a list of users to be merged by the tools/mergeUsers.php .

however it seems like some spammers are registered as authors, so they do have a role.

What we want is to refine the query to list all users who:

  • are authors but dont have a submission, and
  • are not reviewers and dont have any other role in any journal.

The problem is that roles’ table uses an integer to registetr this information in the role_id column. I suspect it is a bitwise encoding, but couldn’t find any documentation about it.

So our query to list suspecious users would be:

select user_id from users where user_id not in
select user_id from roles where role_id = <integer for reviewers, managers, admin, etc..>
select user_id from roles r, articles a  where r.user_id = a.user_id and r.role_id = <integer for authors> 

or something like that…

could anyone help us discover what are the integer codes for the roles mentioned on the query?


Hi @ronniebrito,

The role IDs are defined in lib/pkp/classes/security/

define('ROLE_ID_MANAGER',               0x00000010);
define('ROLE_ID_SITE_ADMIN',            0x00000001);
define('ROLE_ID_SUB_EDITOR',            0x00000011);
define('ROLE_ID_AUTHOR',                0x00010000);
define('ROLE_ID_REVIEWER',              0x00001000);
define('ROLE_ID_ASSISTANT',             0x00001001);
define('ROLE_ID_READER',                0x00100000);
define('ROLE_ID_SUBSCRIPTION_MANAGER',  0x00200000);

(Check your own to make sure you’re looking at the right value! They may vary slightly in different versions of OJS/OMP.)

You can turn these hexadecimal numbers into integers e.g. using PHP:

php -r "echo 0x00200000 . \"\n\";"

Alec Smecher
Public Knowledge Project Team

1 Like

Did you find a refined query to delete (some) spammers that are registered as authors? We have the same problem.


thanks @asmecher,

is 65536

is 1114112

<integer for reviewers, managers, admin, etc…> is always bellow 65536, except when also readers/authors…

@obi, i think the query could be:

select user_id from users where user_id not in
select user_id from roles where role_id < 65536
select r.user_id from roles r, articles a where r.user_id = a.user_id and (r.role_id = 65536 or r.role_id = 1114112)

but did not tested it

Thanks. I will test it this week and report to the forum.


Hi. I have an OJS portal with a half of million users. 99% of them are spam users.

I made some search via users table and merged about 150000 users. It is not difficult to find all users with “porno” or “casino” in url and merge them.

Now i am searching users in user_settings table. I can get not names, but user ids in this table. Is it possible to use mergeUsers.php with user ids? Any way to convert ids to names? I know not enough about sql, and i need a simple tool to make this cleanup.

select username from ojs.users WHERE user_id in (select user_id from ojs.user_settings WHERE setting_value like '%porno%');

Something like this seems to be good…

1 Like

I tested the query but it is wrong:

select user_id from users where user_id not in
select user_id from roles where role_id < 65536
select r.user_id from roles r, articles a where r.user_id = a.user_id and (r.role_id = 65536 or r.role_id = 1114112)

I cannot figure out where you got “articles a”.

hi @obi,

it is an alias for table article , the same is for r and table roles…


I can’t see any table in the database called articles in OJS 3.x

@obi, sorry, I was playing on an OJS2.x database… in OJS 3 not even roles table exist!

on OJS3 you can use tables users and authors to figure out user who are not in authors table…
also user_groups to see if they have other roles (i guess)

I made a username list (in ojs2) by exporting list of URL column and making frequency list (some urls was in 20-30k user profiles) and by exporting usernames wits “stop phrases” in user_settings table.

Now, I deleted about 300K users (via mergeUsers.php, it is enough slow) from this list (only with reader role) and I see about 200K users in my system now. It is no more possible to delete bad users by big groups (there is no stop phrases or bad urls more).

ReCAPTCHA is enough good. It was about 1000 new users per day, and now I see about 10 new users per day.

One more strange moment - more than 90% of bad users was enrolled in one journal (we have 15 journals in our ojs). There was no comments or articles by this authors, but profiles (with porn, casino and other) was in google cache.

Conspiracy theory advocates say this is an “attack” on the journal.