diff --git a/NAMESPACE b/NAMESPACE
index c04ae9b2..9f14a7b9 100644
--- a/NAMESPACE
+++ b/NAMESPACE
@@ -7,9 +7,11 @@ S3method(api_build,op_base_connect)
 S3method(api_build,op_head)
 S3method(as.data.frame,connect_integration_list)
 S3method(as.data.frame,connect_list_hits)
+S3method(as.data.frame,connect_users)
 S3method(as.data.frame,tbl_connect)
 S3method(as_tibble,connect_integration_list)
 S3method(as_tibble,connect_list_hits)
+S3method(as_tibble,connect_users)
 S3method(connect_vars,op_base)
 S3method(connect_vars,op_single)
 S3method(connect_vars,tbl_connect)
@@ -115,6 +117,7 @@ export(get_usage_shiny)
 export(get_usage_static)
 export(get_user_permission)
 export(get_users)
+export(get_users_list)
 export(get_vanity_url)
 export(get_vanity_urls)
 export(get_variant)
diff --git a/R/connect.R b/R/connect.R
index 0631c895..aefb1b43 100644
--- a/R/connect.R
+++ b/R/connect.R
@@ -495,9 +495,10 @@ Connect <- R6::R6Class(
     # users -----------------------------------------------
 
     #' @description Get user details.
-    #' @param guid The user GUID.
+    #' @param guid The user GUID or a `connect_user` object.
     user = function(guid) {
-      self$GET(v1_url("users", guid))
+      guid <- get_user_guid(guid)
+      prepend_class(self$GET(v1_url("users", guid)), "connect_user")
     },
 
     #' @description Get users.
@@ -527,7 +528,11 @@ Connect <- R6::R6Class(
         user_role = user_role,
         account_status = account_status
       )
-      self$GET(path, query = query)
+      res <- self$GET(path, query = query)
+      if (!is.null(res$results)) {
+        res$results <- lapply(res$results, prepend_class, "connect_user")
+      }
+      res
     },
 
     #' @description Get remote users.
@@ -585,8 +590,9 @@ Connect <- R6::R6Class(
     },
 
     #' @description Lock a user.
-    #' @param user_guid User GUID.
-    users_lock = function(user_guid) {
+    #' @param user User GUID or a `connect_user` object.
+    users_lock = function(user) {
+      user_guid <- get_user_guid(user)
       path <- v1_url("users", user_guid, "lock")
       message(path)
       self$POST(
@@ -596,8 +602,9 @@ Connect <- R6::R6Class(
     },
 
     #' @description Unlock a user.
-    #' @param user_guid User GUID.
-    users_unlock = function(user_guid) {
+    #' @param user User GUID or a `connect_user` object.
+    users_unlock = function(user) {
+      user_guid <- get_user_guid(user)
       path <- v1_url("users", user_guid, "lock")
       self$POST(
         path = path,
@@ -606,9 +613,10 @@ Connect <- R6::R6Class(
     },
 
     #' @description Update a user.
-    #' @param user_guid User GUID.
+    #' @param user User GUID or a `connect_user` object.
     #' @param ... User fields.
-    users_update = function(user_guid, ...) {
+    users_update = function(user, ...) {
+      user_guid <- get_user_guid(user)
       path <- v1_url("users", user_guid)
       self$PUT(
         path = path,
@@ -641,16 +649,18 @@ Connect <- R6::R6Class(
 
     #' @description Add a group member.
     #' @param group_guid The group GUID.
-    #' @param user_guid The user GUID.
-    group_member_add = function(group_guid, user_guid) {
+    #' @param user The user GUID or a `connect_user` object.
+    group_member_add = function(group_guid, user) {
+      user_guid <- get_user_guid(user)
       path <- v1_url("groups", group_guid, "members")
       self$POST(path, body = list(user_guid = user_guid))
     },
 
     #' @description Remove a group member.
     #' @param group_guid The group GUID.
-    #' @param user_guid The user GUID.
-    group_member_remove = function(group_guid, user_guid) {
+    #' @param user The user GUID or a `connect_user` object.
+    group_member_remove = function(group_guid, user) {
+      user_guid <- get_user_guid(user)
       path <- v1_url("groups", group_guid, "members", user_guid)
       self$DELETE(path)
     },
diff --git a/R/content.R b/R/content.R
index 355e21d0..e0ddb6d0 100644
--- a/R/content.R
+++ b/R/content.R
@@ -219,10 +219,14 @@ Content <- R6::R6Class(
       self$connect$GET(url)
     },
     #' @description Add a principal to the ACL for this content.
-    #' @param principal_guid GUID for the target user or group.
+    #' @param principal_guid GUID for the target user or group. When
+    #'   `principal_type = "user"`, can also be a `connect_user` object.
     #' @param principal_type Acting on user or group.
     #' @param role The kind of content access.
     permissions_add = function(principal_guid, principal_type, role) {
+      if (principal_type == "user") {
+        principal_guid <- get_user_guid(principal_guid)
+      }
       url <- v1_url("content", self$content$guid, "permissions")
       self$connect$POST(
         url,
@@ -235,10 +239,14 @@ Content <- R6::R6Class(
     },
     #' @description Alter a principal in the ACL for this content.
     #' @param id The target identifier.
-    #' @param principal_guid GUID for the target user or group.
+    #' @param principal_guid GUID for the target user or group. When
+    #'   `principal_type = "user"`, can also be a `connect_user` object.
     #' @param principal_type Acting on user or group.
     #' @param role The kind of content access.
     permissions_update = function(id, principal_guid, principal_type, role) {
+      if (principal_type == "user") {
+        principal_guid <- get_user_guid(principal_guid)
+      }
       url <- v1_url("content", self$content$guid, "permissions", id)
       self$connect$PUT(
         url,
@@ -954,7 +962,6 @@ set_run_as <- function(content, run_as, run_as_current_user = FALSE) {
   return(content)
 }
 
-
 #' Delete Content
 #'
 #' Delete a content item. WARNING: This action deletes all history, configuration,
@@ -1007,8 +1014,8 @@ content_delete <- function(content, force = FALSE) {
 #' @param content An R6 content item
 #' @param ... Settings up update that are passed along to Posit Connect
 #' @param access_type One of "all", "logged_in", or "acl"
-#' @param owner_guid The GUID of a user who is a publisher, so that they can
-#'   become the new owner of the content
+#' @param owner The GUID of a user who is a publisher, so that they can
+#'   become the new owner of the content. Can also be a `connect_user` object.
 #'
 #' @return An R6 content item
 #'
@@ -1040,7 +1047,8 @@ content_update_access_type <- function(
 
 #' @rdname content_update
 #' @export
-content_update_owner <- function(content, owner_guid) {
+content_update_owner <- function(content, owner) {
+  owner_guid <- get_user_guid(owner)
   content_update(content = content, owner_guid = owner_guid)
 }
 
@@ -1103,7 +1111,6 @@ unlock_content <- function(content) {
   return(content)
 }
 
-
 #' Verify Content Name
 #'
 #' Ensures that a content name fits the specifications / requirements of Posit
@@ -1178,7 +1185,6 @@ delete_bundle <- function(content, bundle_id) {
   return(content)
 }
 
-
 #' Content permissions
 #'
 #' Get or set content permissions for a content item
@@ -1199,8 +1205,12 @@ delete_bundle <- function(content, bundle_id) {
 #' This makes it easier to find / isolate this record.
 #'
 #' @param content An R6 content object
-#' @param guid The guid associated with either a user (for `content_add_user`) or group (for `content_add_group`)
-#' @param role The role to assign to a user. Either "viewer" or "owner." Defaults to "viewer"
+#' @param user The guid associated with either a user (for `content_add_user`)
+#'   or group (for `content_add_group`). Can also be a list of `connect_user`
+#'   objects.
+#' @param guid The guid associated with a group.
+#' @param role The role to assign to a user. Either "viewer" or "owner."
+#'   Defaults to "viewer"
 #' @param add_owner Optional. Whether to include the owner in returned
 #'   permission sets. Default is TRUE. The owner will have an NA_character_
 #'   permission "id"
@@ -1209,10 +1219,11 @@ delete_bundle <- function(content, bundle_id) {
 #' @rdname permissions
 #' @family content functions
 #' @export
-content_add_user <- function(content, guid, role = c("viewer", "owner")) {
+content_add_user <- function(content, user, role = c("viewer", "owner")) {
   validate_R6_class(content, "Content")
   role <- .define_role(role)
 
+  guid <- purrr::map_chr(user, get_user_guid)
   purrr::map(guid, ~ .content_add_permission_impl(content, "user", .x, role))
 
   return(content)
@@ -1278,8 +1289,9 @@ content_add_group <- function(content, guid, role = c("viewer", "owner")) {
 
 #' @rdname permissions
 #' @export
-content_delete_user <- function(content, guid) {
+content_delete_user <- function(content, user) {
   validate_R6_class(content, "Content")
+  guid <- purrr::map_chr(user, get_user_guid)
   purrr::map(
     guid,
     ~ .content_delete_permission_impl(
@@ -1331,8 +1343,9 @@ content_delete_group <- function(content, guid) {
 
 #' @rdname permissions
 #' @export
-get_user_permission <- function(content, guid, add_owner = TRUE) {
+get_user_permission <- function(content, user, add_owner = TRUE) {
   validate_R6_class(content, "Content")
+  guid <- get_user_guid(user)
   res <- .get_permission(content, "user", guid, add_owner = add_owner)
   if (length(res) > 0) {
     return(res[[1]])
@@ -1361,7 +1374,6 @@ get_group_permission <- function(content, guid) {
   }
 }
 
-
 #' @rdname permissions
 #' @export
 get_content_permissions <- function(content, add_owner = TRUE) {
diff --git a/R/get.R b/R/get.R
index 01e41f06..cd0affb9 100644
--- a/R/get.R
+++ b/R/get.R
@@ -13,8 +13,8 @@
 #' value (boolean OR). When `NULL` (the default), results are not filtered.
 
 #'
-#' @return
-#' A tibble with the following columns:
+#' @return For `get_users_list`, a list of objects of type `"connect_user"` which
+#'   contain the following information:
 #'
 #'   * `email`: The user's email
 #'   * `username`: The user's username
@@ -33,6 +33,10 @@
 #'   * `locked`: Whether or not the user is locked
 #'   * `guid`: The user's GUID, or unique identifier, in UUID RFC4122 format
 #'
+#' For `get_users`, a data frame with the same fields as `get_users_list`.
+#'
+#' For `get_user`, a single `"connect_user"` object.
+#'
 #' @details
 #' Please see https://docs.posit.co/connect/api/#get-/v1/users for more information.
 #'
@@ -41,7 +45,7 @@
 #' library(connectapi)
 #' client <- connect()
 #'
-#' # Get all users
+#' # Get all users as a data frame
 #' get_users(client)
 #'
 #' # Get all licensed users
@@ -49,10 +53,33 @@
 #'
 #' # Get all users who are administrators or publishers
 #' get_users(client, user_role = c("administrator", "publisher"))
+#'
+#' # Get users as a list
+#' users_list <- get_users_list(client)
 #' }
 #'
 #' @export
-get_users <- function(
+get_users <- function(src,
+                      page_size = 500,
+                      prefix = NULL,
+                      limit = Inf,
+                      user_role = NULL,
+                      account_status = NULL) {
+  as.data.frame(
+    get_users_list(
+      src,
+      page_size,
+      prefix,
+      limit,
+      user_role,
+      account_status
+    )
+  )
+}
+
+#' @rdname get_users
+#' @export
+get_users_list <- function(
   src,
   page_size = 500,
   prefix = NULL,
@@ -72,10 +99,23 @@ get_users <- function(
     ),
     limit = limit
   )
+  return(prepend_class(res, "connect_users"))
+}
 
-  out <- parse_connectapi_typed(res, connectapi_ptypes$users)
+#' @param guid user GUID
+#' @rdname get_users
+get_user <- function(src, guid) {
+  src$user(guid)
+}
 
-  return(out)
+#' @export
+as.data.frame.connect_users <- function(x, ...) {
+  parse_connectapi_typed(x, connectapi_ptypes$users)
+}
+
+#' @export
+as_tibble.connect_users <- function(x, ...) {
+  parse_connectapi_typed(x, connectapi_ptypes$users)
 }
 
 #' Get information about content on the Posit Connect server
diff --git a/R/user.R b/R/user.R
index 90b2122b..a4d415b6 100644
--- a/R/user.R
+++ b/R/user.R
@@ -32,3 +32,28 @@ user_guid_from_username <- function(client, username) {
     return(res[[1]]$guid)
   }
 }
+
+#' Extract User GUID
+#'
+#' Helper function to extract a user GUID from either a character string or a
+#' `connect_user` object.
+#'
+#' @param user Either a character string containing a user GUID or a
+#'   `connect_user` object (as returned by `Connect$user()` or `Connect$users()`)
+#'
+#' @return A character string containing the user GUID
+#'
+#' @keywords internal
+get_user_guid <- function(user) {
+  if (is.character(user)) {
+    return(user)
+  } else if (inherits(user, "connect_user")) {
+    if (!is.null(user$guid)) {
+      return(user$guid)
+    } else {
+      stop("connect_user object does not contain a guid field")
+    }
+  } else {
+    stop("user must be either a character string (GUID) or a connect_user object")
+  }
+}
diff --git a/R/utils.R b/R/utils.R
index 85e89b4e..be60518d 100644
--- a/R/utils.R
+++ b/R/utils.R
@@ -256,3 +256,9 @@ message_if_not_testing <- function(...) {
     message(...)
   }
 }
+
+# Prepends a new class to a given objec
+prepend_class <- function(x, class) {
+  class(x) <- c(class, class(x))
+  x
+}
diff --git a/man/Content.Rd b/man/Content.Rd
index e8b45a1b..8e222bce 100644
--- a/man/Content.Rd
+++ b/man/Content.Rd
@@ -334,7 +334,8 @@ Add a principal to the ACL for this content.
 \subsection{Arguments}{
 \if{html}{\out{
}}
 \describe{
-\item{\code{principal_guid}}{GUID for the target user or group.}
+\item{\code{principal_guid}}{GUID for the target user or group. When
+\code{principal_type = "user"}, can also be a \code{connect_user} object.}
 
 \item{\code{principal_type}}{Acting on user or group.}
 
@@ -357,7 +358,8 @@ Alter a principal in the ACL for this content.
 \describe{
 \item{\code{id}}{The target identifier.}
 
-\item{\code{principal_guid}}{GUID for the target user or group.}
+\item{\code{principal_guid}}{GUID for the target user or group. When
+\code{principal_type = "user"}, can also be a \code{connect_user} object.}
 
 \item{\code{principal_type}}{Acting on user or group.}
 
diff --git a/man/PositConnect.Rd b/man/PositConnect.Rd
index 241b0c9a..47c2267f 100644
--- a/man/PositConnect.Rd
+++ b/man/PositConnect.Rd
@@ -767,7 +767,7 @@ Get user details.
 \subsection{Arguments}{
 \if{html}{\out{
}}
 \describe{
-\item{\code{guid}}{The user GUID.}
+\item{\code{guid}}{The user GUID or a \code{connect_user} object.}
 }
 \if{html}{\out{
}}
 }
@@ -883,13 +883,13 @@ Create a remote user.
 \subsection{Method \code{users_lock()}}{
 Lock a user.
 \subsection{Usage}{
-\if{html}{\out{
}}\preformatted{Connect$users_lock(user_guid)}\if{html}{\out{
}}
+\if{html}{\out{
}}\preformatted{Connect$users_lock(user)}\if{html}{\out{
}}
 }
 
 \subsection{Arguments}{
 \if{html}{\out{
}}
 \describe{
-\item{\code{user_guid}}{User GUID.}
+\item{\code{user}}{User GUID or a \code{connect_user} object.}
 }
 \if{html}{\out{
}}
 }
@@ -900,13 +900,13 @@ Lock a user.
 \subsection{Method \code{users_unlock()}}{
 Unlock a user.
 \subsection{Usage}{
-\if{html}{\out{
}}\preformatted{Connect$users_unlock(user_guid)}\if{html}{\out{
}}
+\if{html}{\out{
}}\preformatted{Connect$users_unlock(user)}\if{html}{\out{
}}
 }
 
 \subsection{Arguments}{
 \if{html}{\out{
}}
 \describe{
-\item{\code{user_guid}}{User GUID.}
+\item{\code{user}}{User GUID or a \code{connect_user} object.}
 }
 \if{html}{\out{
}}
 }
@@ -917,13 +917,13 @@ Unlock a user.
 \subsection{Method \code{users_update()}}{
 Update a user.
 \subsection{Usage}{
-\if{html}{\out{
}}\preformatted{Connect$users_update(user_guid, ...)}\if{html}{\out{
}}
+\if{html}{\out{
}}\preformatted{Connect$users_update(user, ...)}\if{html}{\out{
}}
 }
 
 \subsection{Arguments}{
 \if{html}{\out{
}}
 \describe{
-\item{\code{user_guid}}{User GUID.}
+\item{\code{user}}{User GUID or a \code{connect_user} object.}
 
 \item{\code{...}}{User fields.}
 }
@@ -974,7 +974,7 @@ Get group members.
 \subsection{Method \code{group_member_add()}}{
 Add a group member.
 \subsection{Usage}{
-\if{html}{\out{
}}\preformatted{Connect$group_member_add(group_guid, user_guid)}\if{html}{\out{
}}
+\if{html}{\out{
}}\preformatted{Connect$group_member_add(group_guid, user)}\if{html}{\out{
}}
 }
 
 \subsection{Arguments}{
@@ -982,7 +982,7 @@ Add a group member.
 \describe{
 \item{\code{group_guid}}{The group GUID.}
 
-\item{\code{user_guid}}{The user GUID.}
+\item{\code{user}}{The user GUID or a \code{connect_user} object.}
 }
 \if{html}{\out{
}}
 }
@@ -993,7 +993,7 @@ Add a group member.
 \subsection{Method \code{group_member_remove()}}{
 Remove a group member.
 \subsection{Usage}{
-\if{html}{\out{
}}\preformatted{Connect$group_member_remove(group_guid, user_guid)}\if{html}{\out{
}}
+\if{html}{\out{
}}\preformatted{Connect$group_member_remove(group_guid, user)}\if{html}{\out{
}}
 }
 
 \subsection{Arguments}{
@@ -1001,7 +1001,7 @@ Remove a group member.
 \describe{
 \item{\code{group_guid}}{The group GUID.}
 
-\item{\code{user_guid}}{The user GUID.}
+\item{\code{user}}{The user GUID or a \code{connect_user} object.}
 }
 \if{html}{\out{
}}
-\if{html}{\out{}}
-\if{latex}{\out{\hypertarget{method-Variant-get_variant}{}}}
-\subsection{Method \code{get_variant()}}{
-Get the underlying variant data.
-\subsection{Usage}{
-\if{html}{\out{}}\preformatted{Variant$get_variant()}\if{html}{\out{
}}
-}
-
-}
-\if{html}{\out{
}}
 \if{html}{\out{}}
 \if{latex}{\out{\hypertarget{method-Variant-get_variant_remote}{}}}
 \subsection{Method \code{get_variant_remote()}}{
+Get the underlying variant data.
+
+
 Get and store the (remote) variant data.
 \subsection{Usage}{
 \if{html}{\out{}}\preformatted{Variant$get_variant_remote()}\if{html}{\out{
}}
diff --git a/man/VariantSchedule.Rd b/man/VariantSchedule.Rd
index ee961e1a..d7c23e29 100644
--- a/man/VariantSchedule.Rd
+++ b/man/VariantSchedule.Rd
@@ -81,7 +81,6 @@ Other R6 classes:
 connectapi::Variant$get_subscribers()
 connectapi::Variant$get_url()
 connectapi::Variant$get_url_rev()
-connectapi::Variant$get_variant()
 connectapi::Variant$get_variant_remote()
 connectapi::Variant$job()
 connectapi::Variant$jobs()
diff --git a/man/VariantTask.Rd b/man/VariantTask.Rd
index 4f23bd9e..316a53a1 100644
--- a/man/VariantTask.Rd
+++ b/man/VariantTask.Rd
@@ -80,7 +80,6 @@ Other R6 classes:
 connectapi::Variant$get_subscribers()
 connectapi::Variant$get_url()
 connectapi::Variant$get_url_rev()
-connectapi::Variant$get_variant()
 connectapi::Variant$get_variant_remote()
 connectapi::Variant$job()
 connectapi::Variant$jobs()
diff --git a/man/content_update.Rd b/man/content_update.Rd
index ef1254e8..a5e49522 100644
--- a/man/content_update.Rd
+++ b/man/content_update.Rd
@@ -10,7 +10,7 @@ content_update(content, ...)
 
 content_update_access_type(content, access_type = c("all", "logged_in", "acl"))
 
-content_update_owner(content, owner_guid)
+content_update_owner(content, owner)
 }
 \arguments{
 \item{content}{An R6 content item}
@@ -19,8 +19,8 @@ content_update_owner(content, owner_guid)
 
 \item{access_type}{One of "all", "logged_in", or "acl"}
 
-\item{owner_guid}{The GUID of a user who is a publisher, so that they can
-become the new owner of the content}
+\item{owner}{The GUID of a user who is a publisher, so that they can
+become the new owner of the content. Can also be a \code{connect_user} object.}
 }
 \value{
 An R6 content item
diff --git a/man/get_user_guid.Rd b/man/get_user_guid.Rd
new file mode 100644
index 00000000..b69472f0
--- /dev/null
+++ b/man/get_user_guid.Rd
@@ -0,0 +1,20 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/user.R
+\name{get_user_guid}
+\alias{get_user_guid}
+\title{Extract User GUID}
+\usage{
+get_user_guid(user)
+}
+\arguments{
+\item{user}{Either a character string containing a user GUID or a
+\code{connect_user} object (as returned by \code{Connect$user()} or \code{Connect$users()})}
+}
+\value{
+A character string containing the user GUID
+}
+\description{
+Helper function to extract a user GUID from either a character string or a
+\code{connect_user} object.
+}
+\keyword{internal}
diff --git a/man/get_users.Rd b/man/get_users.Rd
index 456b8afa..abb497bb 100644
--- a/man/get_users.Rd
+++ b/man/get_users.Rd
@@ -2,6 +2,8 @@
 % Please edit documentation in R/get.R
 \name{get_users}
 \alias{get_users}
+\alias{get_users_list}
+\alias{get_user}
 \title{Get user information from the Posit Connect server}
 \usage{
 get_users(
@@ -12,6 +14,17 @@ get_users(
   user_role = NULL,
   account_status = NULL
 )
+
+get_users_list(
+  src,
+  page_size = 500,
+  prefix = NULL,
+  limit = Inf,
+  user_role = NULL,
+  account_status = NULL
+)
+
+get_user(src, guid)
 }
 \arguments{
 \item{src}{The source object}
@@ -30,9 +43,12 @@ The filter is case insensitive.}
 \item{account_status}{Optionally filter by account status ("locked",
 "licensed", "inactive"). Pass a vector of multiple statuses to match any
 value (boolean OR). When \code{NULL} (the default), results are not filtered.}
+
+\item{guid}{user GUID}
 }
 \value{
-A tibble with the following columns:
+For \code{get_users_list}, a list of objects of type \code{"connect_user"} which
+contain the following information:
 \itemize{
 \item \code{email}: The user's email
 \item \code{username}: The user's username
@@ -51,6 +67,10 @@ through an email. This feature is unique to password authentication.
 \item \code{locked}: Whether or not the user is locked
 \item \code{guid}: The user's GUID, or unique identifier, in UUID RFC4122 format
 }
+
+For \code{get_users}, a data frame with the same fields as \code{get_users_list}.
+
+For \code{get_user}, a single \code{"connect_user"} object.
 }
 \description{
 Get user information from the Posit Connect server
@@ -63,7 +83,7 @@ Please see https://docs.posit.co/connect/api/#get-/v1/users for more information
 library(connectapi)
 client <- connect()
 
-# Get all users
+# Get all users as a data frame
 get_users(client)
 
 # Get all licensed users
@@ -71,6 +91,9 @@ get_users(client, account_status = "licensed")
 
 # Get all users who are administrators or publishers
 get_users(client, user_role = c("administrator", "publisher"))
+
+# Get users as a list
+users_list <- get_users_list(client)
 }
 
 }
diff --git a/man/permissions.Rd b/man/permissions.Rd
index 1af0aff9..0db517a4 100644
--- a/man/permissions.Rd
+++ b/man/permissions.Rd
@@ -12,15 +12,15 @@
 \alias{get_content_permissions}
 \title{Content permissions}
 \usage{
-content_add_user(content, guid, role = c("viewer", "owner"))
+content_add_user(content, user, role = c("viewer", "owner"))
 
 content_add_group(content, guid, role = c("viewer", "owner"))
 
-content_delete_user(content, guid)
+content_delete_user(content, user)
 
 content_delete_group(content, guid)
 
-get_user_permission(content, guid, add_owner = TRUE)
+get_user_permission(content, user, add_owner = TRUE)
 
 get_my_permission(content, add_owner = TRUE)
 
@@ -31,9 +31,14 @@ get_content_permissions(content, add_owner = TRUE)
 \arguments{
 \item{content}{An R6 content object}
 
-\item{guid}{The guid associated with either a user (for \code{content_add_user}) or group (for \code{content_add_group})}
+\item{user}{The guid associated with either a user (for \code{content_add_user})
+or group (for \code{content_add_group}). Can also be a list of \code{connect_user}
+objects.}
 
-\item{role}{The role to assign to a user. Either "viewer" or "owner." Defaults to "viewer"}
+\item{role}{The role to assign to a user. Either "viewer" or "owner."
+Defaults to "viewer"}
+
+\item{guid}{The guid associated with a group.}
 
 \item{add_owner}{Optional. Whether to include the owner in returned
 permission sets. Default is TRUE. The owner will have an NA_character_
diff --git a/tests/integrated/test-get.R b/tests/integrated/test-get.R
index 0e41106c..860bf909 100644
--- a/tests/integrated/test-get.R
+++ b/tests/integrated/test-get.R
@@ -8,7 +8,6 @@ cont1_content <- NULL
 
 test_that("get_users works", {
   users <- get_users(test_conn_1)
-
   expect_s3_class(users, c("tbl_df", "tbl", "data.frame"))
   expect_equal(
     purrr::map_chr(vctrs::vec_ptype(users), typeof),
diff --git a/tests/testthat/test-content.R b/tests/testthat/test-content.R
index f7e8e434..466a91d6 100644
--- a/tests/testthat/test-content.R
+++ b/tests/testthat/test-content.R
@@ -144,6 +144,32 @@ with_mock_api({
       "https://connect.example/__api__/v1/content/f2f37341-e21d-3d80-c698-a935ad614066/permissions"
     )
   })
+
+  test_that("Content$permissions_add() accepts connect_user object for users", {
+    con <- Connect$new(server = "https://connect.example", api_key = "fake")
+    item <- content_item(con, "f2f37341-e21d-3d80-c698-a935ad614066")
+    user_guid <- "a01792e3-2e67-402e-99af-be04a48da074"
+    user <- structure(list(guid = user_guid), class = "connect_user")
+
+    expect_POST(
+      item$permissions_add(user, "user", "viewer"),
+      "https://connect.example/__api__/v1/content/f2f37341-e21d-3d80-c698-a935ad614066/permissions",
+      '{"principal_guid":"a01792e3-2e67-402e-99af-be04a48da074","principal_type":"user","role":"viewer"}'
+    )
+  })
+
+  test_that("Content$permissions_update() accepts connect_user object for users", {
+    con <- Connect$new(server = "https://connect.example", api_key = "fake")
+    item <- content_item(con, "f2f37341-e21d-3d80-c698-a935ad614066")
+    user_guid <- "a01792e3-2e67-402e-99af-be04a48da074"
+    user <- structure(list(guid = user_guid), class = "connect_user")
+
+    expect_PUT(
+      item$permissions_update(94, user, "user", "editor"),
+      "https://connect.example/__api__/v1/content/f2f37341-e21d-3d80-c698-a935ad614066/permissions/94",
+      '{"principal_guid":"a01792e3-2e67-402e-99af-be04a48da074","principal_type":"user","role":"editor"}'
+    )
+  })
 })
 
 without_internet({
@@ -467,6 +493,55 @@ test_that("get_content_packages() gets packages", {
   })
 })
 
+with_mock_api({
+  test_that("content_add_user() accepts connect_user object", {
+    con <- Connect$new(server = "https://connect.example", api_key = "fake")
+    item <- content_item(con, "f2f37341-e21d-3d80-c698-a935ad614066")
+    user_guid <- "a01792e3-2e67-402e-99af-be04a48da074"
+    user <- structure(list(guid = user_guid), class = "connect_user")
+
+    expect_POST(
+      suppressMessages(content_add_user(item, user)),
+      "https://connect.example/__api__/v1/content/f2f37341-e21d-3d80-c698-a935ad614066/permissions"
+    )
+
+    # Test with list of connect_user objects
+    user2 <- structure(list(guid = "b02892e3-2e67-402e-99af-be04a48da075"), class = "connect_user")
+    expect_POST(
+      suppressMessages(content_add_user(item, list(user, user2))),
+      "https://connect.example/__api__/v1/content/f2f37341-e21d-3d80-c698-a935ad614066/permissions"
+    )
+  })
+
+  test_that("get_user_permission() accepts connect_user object", {
+    con <- Connect$new(server = "https://connect.example", api_key = "fake")
+    item <- content_item(con, "f2f37341-e21d-3d80-c698-a935ad614066")
+    user_guid <- "a01792e3-2e67-402e-99af-be04a48da074"
+    user <- structure(list(guid = user_guid), class = "connect_user")
+
+    # Test with GUID string - should work
+    perm1 <- suppressMessages(get_user_permission(item, user_guid))
+
+    # Test with connect_user object - should also work and return same result
+    perm2 <- suppressMessages(get_user_permission(item, user))
+
+    # Both should return the same permission (or NULL if not found)
+    expect_equal(perm1, perm2)
+  })
+
+  test_that("content_update_owner() accepts connect_user object", {
+    con <- Connect$new(server = "https://connect.example", api_key = "fake")
+    item <- content_item(con, "f2f37341-e21d-3d80-c698-a935ad614066")
+    user_guid <- "a01792e3-2e67-402e-99af-be04a48da074"
+    user <- structure(list(guid = user_guid), class = "connect_user")
+
+    expect_PATCH(
+      suppressMessages(content_update_owner(item, user)),
+      "https://connect.example/__api__/v1/content/f2f37341-e21d-3d80-c698-a935ad614066"
+    )
+  })
+})
+
 # content search ----
 
 with_mock_dir("2025.09.0", {
diff --git a/tests/testthat/test-groups.R b/tests/testthat/test-groups.R
index 957cd3b3..6fa5319c 100644
--- a/tests/testthat/test-groups.R
+++ b/tests/testthat/test-groups.R
@@ -34,6 +34,30 @@ without_internet({
       "https://connect.example/__api__/v1/groups/remote?limit=500"
     )
   })
+
+  test_that("group_member_add() accepts connect_user object", {
+    client <- Connect$new(server = "https://connect.example", api_key = "fake")
+    group_guid <- "a6fb5cea-0123-4567-89ab-cdef01234567"
+    user_guid <- "20a79ce3-6e87-4522-9faf-be24228800a4"
+    user <- structure(list(guid = user_guid), class = "connect_user")
+
+    expect_POST(
+      client$group_member_add(group_guid, user),
+      "https://connect.example/__api__/v1/groups/a6fb5cea-0123-4567-89ab-cdef01234567/members"
+    )
+  })
+
+  test_that("group_member_remove() accepts connect_user object", {
+    client <- Connect$new(server = "https://connect.example", api_key = "fake")
+    group_guid <- "a6fb5cea-0123-4567-89ab-cdef01234567"
+    user_guid <- "20a79ce3-6e87-4522-9faf-be24228800a4"
+    user <- structure(list(guid = user_guid), class = "connect_user")
+
+    expect_DELETE(
+      client$group_member_remove(group_guid, user),
+      "https://connect.example/__api__/v1/groups/a6fb5cea-0123-4567-89ab-cdef01234567/members/20a79ce3-6e87-4522-9faf-be24228800a4" # nolint
+    )
+  })
 })
 
 with_mock_dir("2024.08.0", {
diff --git a/tests/testthat/test-users.R b/tests/testthat/test-users.R
index 23f45c5b..60f6f455 100644
--- a/tests/testthat/test-users.R
+++ b/tests/testthat/test-users.R
@@ -1,3 +1,47 @@
+# Tests for get_user_guid() helper function ----
+
+test_that("get_user_guid() works with character GUID", {
+  guid <- "20a79ce3-6e87-4522-9faf-be24228800a4"
+  expect_equal(get_user_guid(guid), guid)
+})
+
+test_that("get_user_guid() works with connect_user object", {
+  user <- structure(
+    list(
+      guid = "20a79ce3-6e87-4522-9faf-be24228800a4",
+      username = "carlos12",
+      first_name = "Carlos",
+      last_name = "User"
+    ),
+    class = "connect_user"
+  )
+  expect_equal(get_user_guid(user), "20a79ce3-6e87-4522-9faf-be24228800a4")
+})
+
+test_that("get_user_guid() errors with invalid input", {
+  expect_error(
+    get_user_guid(123),
+    "user must be either a character string \\(GUID\\) or a connect_user object"
+  )
+  expect_error(
+    get_user_guid(list(name = "test")),
+    "user must be either a character string \\(GUID\\) or a connect_user object"
+  )
+})
+
+test_that("get_user_guid() errors when connect_user has no guid field", {
+  user <- structure(
+    list(username = "carlos12"),
+    class = "connect_user"
+  )
+  expect_error(
+    get_user_guid(user),
+    "connect_user object does not contain a guid field"
+  )
+})
+
+# Tests for Connect client methods accepting connect_user objects ----
+
 with_mock_api({
   test_that("we can retrieve the users list", {
     con <- Connect$new(server = "https://connect.example", api_key = "fake")
@@ -6,6 +50,13 @@ with_mock_api({
     expect_equal(nrow(users), 3)
   })
 
+  test_that("we can retrieve the users list as list", {
+    con <- Connect$new(server = "https://connect.example", api_key = "fake")
+    users <- get_users_list(con)
+    expect_s3_class(users, "connect_users")
+    expect_equal(length(users), 3)
+  })
+
   test_that("we can retrieve a user by id", {
     con <- Connect$new(server = "https://connect.example", api_key = "fake")
     a_user <- con$user("20a79ce3-6e87-4522-9faf-be24228800a4")
@@ -13,6 +64,14 @@ with_mock_api({
     expect_type(a_user, "list")
     expect_equal(a_user$first_name, "Carlos")
   })
+
+  test_that("Connect$user() accepts connect_user object", {
+    con <- Connect$new(server = "https://connect.example", api_key = "fake")
+    user <- con$user("20a79ce3-6e87-4522-9faf-be24228800a4")
+    same_user <- con$user(user)
+    expect_equal(same_user$guid, "20a79ce3-6e87-4522-9faf-be24228800a4")
+    expect_equal(same_user$first_name, "Carlos")
+  })
 })
 
 without_internet({
@@ -43,4 +102,52 @@ without_internet({
       "https://connect.example/__api__/v1/users/remote?prefix=A%20user%20name"
     )
   })
+
+  test_that("users_lock() accepts connect_user object or GUID string", {
+    client <- Connect$new(server = "https://connect.example", api_key = "fake")
+    user_guid <- "20a79ce3-6e87-4522-9faf-be24228800a4"
+    user <- structure(list(guid = user_guid), class = "connect_user")
+
+    expect_POST(
+      client$users_lock(user_guid),
+      "https://connect.example/__api__/v1/users/20a79ce3-6e87-4522-9faf-be24228800a4/lock"
+    )
+
+    expect_POST(
+      client$users_lock(user),
+      "https://connect.example/__api__/v1/users/20a79ce3-6e87-4522-9faf-be24228800a4/lock"
+    )
+  })
+
+  test_that("users_unlock() accepts connect_user object or GUID string", {
+    client <- Connect$new(server = "https://connect.example", api_key = "fake")
+    user_guid <- "20a79ce3-6e87-4522-9faf-be24228800a4"
+    user <- structure(list(guid = user_guid), class = "connect_user")
+
+    expect_POST(
+      client$users_unlock(user_guid),
+      "https://connect.example/__api__/v1/users/20a79ce3-6e87-4522-9faf-be24228800a4/lock"
+    )
+
+    expect_POST(
+      client$users_unlock(user),
+      "https://connect.example/__api__/v1/users/20a79ce3-6e87-4522-9faf-be24228800a4/lock"
+    )
+  })
+
+  test_that("users_update() accepts connect_user object or GUID string", {
+    client <- Connect$new(server = "https://connect.example", api_key = "fake")
+    user_guid <- "20a79ce3-6e87-4522-9faf-be24228800a4"
+    user <- structure(list(guid = user_guid), class = "connect_user")
+
+    expect_PUT(
+      client$users_update(user_guid, first_name = "New Name"),
+      "https://connect.example/__api__/v1/users/20a79ce3-6e87-4522-9faf-be24228800a4"
+    )
+
+    expect_PUT(
+      client$users_update(user, first_name = "New Name"),
+      "https://connect.example/__api__/v1/users/20a79ce3-6e87-4522-9faf-be24228800a4"
+    )
+  })
 })