JetEngine Relation Between Users and CPT: Link Users on the Front End

To set up a JetEngine relation between users and a CPT on the front end, build a JetFormBuilder form with a Checkbox (or Radio) field filled from a JetEngine Users query. Then add two post-submit actions. Insert/Update Post creates the post, and Connect Relation Items links the chosen users to it. Most mistakes come from putting the wrong ID into Parent Item ID or Child Item ID, and the right choice depends on which side of your relation is the parent.
A second form on the single post page edits the linked users, with the current links already ticked. The example throughout is a Courses CPT with instructor users.
- What you need before you start
- Step 1: Check the relation's direction and type
- Step 2: Create a Users query
- Step 3: Build the create-and-link form
- Step 4: Add the post-submit actions
- Step 5: Edit the linked users of an existing post
- Step 6: Test the forms
- Troubleshooting
- Optional: check the link with PHP
What you need before you start
- JetEngine and JetFormBuilder, both active.
- A CPT, here Courses.
- A relation in JetEngine > Relations between Courses and Users, with linking already working in the admin.
The related items box on the edit screen only exists in the WordPress admin. On the front end, nothing gets linked unless a form does it. The front-end equivalent is the Connect Relation Items post-submit action in JetFormBuilder. Its settings are Relation, Parent Item ID, Child Item ID, Update Context and How to Store the New Item. Crocoblock documents it with post-to-post examples in Connecting WordPress Related Items with Post Submit Actions. The same action works when Users is one side of the relation.
Step 1: Check the relation's direction and type
Every later setting depends on this. Open JetEngine > Relations, edit your relation, and note:
- The parent object. Is it Courses or Users?
- The relation type. One to one, one to many, or many to many?
- The relation ID. Check it in the relation's edit page URL, or as listed in JetEngine > Relations in your version. You only need it for the optional PHP check.
Radio and Select allow one choice. Checkbox allows one or more. Pick the field type by how many instructors one course can have:
| Relation | Instructors per course | Field type |
|---|---|---|
| Courses is parent, one to many | Several (each user belongs to one course) | Checkbox |
| Users is parent, one to many | One (each user can have many courses) | Radio or Select |
| Many to many | Several | Checkbox |
| One to one | One | Radio or Select |
If one instructor teaches several courses and each course has several instructors, the relation has to be many to many. Change it before you build the forms.
Step 2: Create a Users query
- Go to JetEngine > Query Builder > Add New.
- Set the query type to Users and name it, for example Instructors.
- To narrow the list, for example to one role, set the parameters in the Users Query section. Leave it empty to list every user.
- Save the query and note its ID, which you check in the edit page URL. Crocoblock's example URL ends in
admin.php?page=jet-engine-query&query_action=edit&id=2, where the ID is 2. - Look at the Query Preview section. It shows which users the query returns and the column names you can use as labels, such as
user_login,user_nicenameanduser_email.
If the preview is empty, fix the query first. Our list of common JetEngine Query Builder mistakes covers the usual causes.
Step 3: Build the create-and-link form
- Go to JetFormBuilder > Add New and name the form Add Course.
- Add a Text field named
course_title. - Add a Checkbox field named
instructors, or a Radio or Select field if Step 1 pointed you to one. - In the instructors field settings, set:
- Fill Options From to Generate Dynamically.
- Generator Function to Get values list from JetEngine Query.
- Field Name to
12|ID|user_login, with your own query ID in place of 12.
The three parts of the generator string are the query ID, the user property used as the option value, and the property used as the label. Always keep ID in the middle, because Connect Relation Items needs user IDs. Crocoblock's guide to populating a form field with a list of users uses the same pattern.
Field names must be unique and use only lowercase Latin letters with no spaces. Separate words with - or _.
Step 4: Add the post-submit actions
Open JetForm settings > Post Submit Actions and add two actions in this order, editing each with the pencil icon. Insert/Update Post must run first, so the new course ID exists when Connect Relation Items reads it.
Action 1: Insert/Update Post
- Post Type: Courses.
- Post Status: Published.
- Fields Map: map
course_titleto Post Title. Leaveinstructorsat its default, unmapped.
If the relation field is in the Fields Map, new related items replace the old ones instead of being added.
Action 2: Connect Relation Items
- Relation: your Courses and Users relation.
- Parent Item ID and Child Item ID: use the table below.
- Update Context: leave it empty for a new post.
- How to Store the New Item: pick the option that fits, adding to or replacing existing links, as it is labeled in your version.
| Your relation | Parent Item ID | Child Item ID |
|---|---|---|
| Courses is parent, Users is child | The inserted post ID option | instructors |
| Users is parent, Courses is child | instructors | The inserted post ID option |
Place the form on a page that only people allowed to create courses can open. Anyone who sees the form also sees the full user list and its labels.
Step 5: Edit the linked users of an existing post
Create a second form, Edit Course Instructors, and place it on the single Course template.
- Add a Hidden field named
post_idand keep its default value, Current Post ID. - Add the
instructorsfield with the same field type and generator string as in Step 3. - In its Default Value, open Dynamic tags and choose Source = Related Items. Set From Relation to your relation, From Object to the users' side (Child object when Courses is the parent, Parent object when Users is the parent), and Initial Object ID From to Current Object ID. Users already linked to the course now appear ticked.
Action 1: Connect Relation Items
| Your relation | Parent Item ID | Child Item ID | Update Context |
|---|---|---|---|
| Courses is parent | post_id | instructors | We updating children items for the parent object |
| Users is parent | instructors | post_id | We updating parent items for the child object |
Set How to Store the New Item to the option in your version that matches what the form should do.
Action 2: Insert/Update Post
- Post Type: Courses.
- Fields Map: map
post_idto Post ID (will update the post). Leaveinstructorsunmapped.
Without that Post ID mapping, Insert/Update Post inserts a new post on every submit, so each edit would create a duplicate course.
Ticked users stay linked, newly ticked users get linked, and unticking a user disconnects them from the course.
Restrict who can see the edit form
The single Course template is public, so show this form only to users allowed to manage courses. Use the form's or Elementor's visibility or conditional settings, or a template only those users can reach. Hiding the form only controls what is displayed. It is not a full server-side permission check, so keep the form off pages that untrusted visitors can load. Do not use user_email as the label on any page the public might see.
Step 6: Test the forms
- Open the Add Course page, enter a title, tick two instructors and submit.
- In the admin, open the new course. Its relation box should list both users.
- Open the course's single page as an allowed user. The edit form should show the same two users ticked.
- Untick one, submit, then reload the course edit screen. Only one user should remain linked.
- Check the Courses list has the same number of posts. A new course means
post_idis not mapped to Post ID (will update the post). - Log out and open the course. The edit form should not appear.
To show the linked instructors to visitors, see our tip on how to show related users on a single post with a relation.
Troubleshooting
- The post is created but no users are linked, or links land on the wrong side. Parent and child are swapped. Check the parent object and remap using the table in Step 4.
- Older instructors vanish when new ones are added. The user field is mapped in the Insert/Update Post Fields Map, or How to Store the New Item is set to replace. Leave the field unmapped and check that setting.
- Each edit creates a new course. Map the
post_idHidden field to Post ID (will update the post). - Users disappear after a front-end edit. Unticked users are disconnected. Without the Related Items preset, every submit starts from an empty field.
- The wrong number of users can be picked. The field type does not match the relation. See Step 1.
- Unclear labels. Change the third part of the generator string, for example
12|ID|user_nicename, after checking column names in Query Preview. - No options, or the value is not saved. Check the query ID in the generator string and that the field name is unique, lowercase Latin, with no spaces.
Optional: check the link with PHP
When the admin box and the front end disagree, this shortcode reads the relation through the JetEngine relations API and lists the linked users to administrators only. JetEngine relations are available from init priority 12. The lookup runs inside the shortcode callback when the page renders, which is long after that, so registering the shortcode on init at the default priority is safe.
Paste this into a code snippets plugin or your child theme's functions.php file. Do not include an opening <?php tag in a snippets plugin; add one only at the top of a new, empty functions.php file:
Code
/**
* BoostMyCroco: admin-only check of the users linked to a post through a JetEngine relation.
* Usage on a single post: [bmc_relation_check rel_id="5" side="children"]
* side="children" when the CPT is the parent, side="parents" when Users is the parent.
*/
add_action( 'init', 'bmc_register_relation_check_shortcode' );
function bmc_register_relation_check_shortcode() {
add_shortcode( 'bmc_relation_check', 'bmc_relation_check_shortcode' );
}
function bmc_relation_check_shortcode( $atts ) {
// Only administrators see the output.
if ( ! current_user_can( 'manage_options' ) ) {
return '';
}
if ( ! function_exists( 'jet_engine' ) || empty( jet_engine()->relations ) ) {
return '<p>JetEngine relations are not available.</p>';
}
$atts = shortcode_atts(
array(
'rel_id' => 0,
'post_id' => 0,
'side' => 'children',
),
$atts,
'bmc_relation_check'
);
$rel_id = absint( $atts['rel_id'] );
$post_id = absint( $atts['post_id'] );
if ( ! $post_id ) {
$post_id = absint( get_the_ID() );
}
$side = ( 'parents' === sanitize_key( $atts['side'] ) ) ? 'parents' : 'children';
if ( ! $rel_id || ! $post_id ) {
return '<p>Set rel_id, and post_id when the shortcode is not on a single post.</p>';
}
// Runs at render time, long after init priority 12, so relations are registered.
$relation = jet_engine()->relations->get_active_relations( $rel_id );
if ( ! is_object( $relation ) || ! method_exists( $relation, 'get_children' ) || ! method_exists( $relation, 'get_parents' ) ) {
return '<p>' . esc_html( sprintf( 'No active relation with ID %d.', $rel_id ) ) . '</p>';
}
// Read only. This snippet never writes or deletes relation rows.
$ids = ( 'parents' === $side )
? $relation->get_parents( $post_id, 'ids' )
: $relation->get_children( $post_id, 'ids' );
$ids = array_filter( array_map( 'absint', (array) $ids ) );
if ( empty( $ids ) ) {
return '<p>' . esc_html( sprintf( 'Post %d has no linked users in relation %d.', $post_id, $rel_id ) ) . '</p>';
}
$output = '<ul>';
foreach ( $ids as $user_id ) {
$user = get_userdata( $user_id );
$label = $user ? $user->user_login . ' (ID ' . $user_id . ')' : 'Unknown user (ID ' . $user_id . ')';
$output .= '<li>' . esc_html( $label ) . '</li>';
}
$output .= '</ul>';
return $output;
}How to use it
- Find the relation ID in the relation's edit page URL, or as listed in JetEngine > Relations in your version.
- Add
[bmc_relation_check rel_id="5" side="children"]to the single Course template, for example with a Shortcode widget, using your relation ID instead of 5. - Use
side="children"when Courses is the parent (the snippet callsget_children()) andside="parents"when Users is the parent (it callsget_parents()). - Outside a single post, add a post ID, for example
post_id="123".
What you should see
After a test submit, open the course as an administrator. You should see user logins with their IDs, matching the admin relation box. Other visitors see nothing. If the list is empty but the admin box shows users, switch the side value. To confirm the error handling, try a relation ID that does not exist: you should get the No active relation message.
The snippet only reads data. It never calls delete_rows(), which deletes every row of a relation when both IDs are empty, so keep that method out of debugging code. Remove the shortcode once the forms work.
With one create form, one restricted edit form and the parent/child mapping matched to your relation, front-end users and CPT posts stay linked the same way the admin links them. If you would rather have this built and tested for you, post a request on BoostMyCroco.
Frequently Asked Questions
Can I link users to a JetEngine CCT item instead of a CPT post?
Why does my JetFormBuilder form create the post but not link any users?
Does unticking a user in the edit form delete the user or the post?
Can I show user nicknames or emails in the form instead of logins?
Does the order of the post-submit actions matter?
This guide answers a question from our community: Criar Relation Between Users and CPT
Sources
- JetFormBuilder: Connecting WordPress Related Items with Post Submit Actions (jetformbuilder.com)
- How to Populate a Form Field with a List of Users (jetformbuilder.com)
- JetFormBuilder: Insert/Update Post Action Overview | Crocoblock (jetformbuilder.com)
- How to Connect Users to Custom Content Type Items via Form — JetEngine (crocoblock.com)
- JetFormBuilder & JetEngine Combo: 10 PRO Features to Supercharge Forms - Crocoblock (crocoblock.com)
- Get JetEngine Related Items programmatically / Update related items / Get/Update relation meta · GitHub (gist.github.com)


