Hi all,
I want to allow logged-in users to save pages to their user account, so they can access them from their user profile page. This would function similarly to the "Reading List" on nytimes.com or "Saved Stories" on washingtonpost.com.
Are there existing AEM add-ons or plugins that facilitate this, or are there other recommended solutions?
Thanks!
Topics help categorize Community content and increase your ability to discover relevant content.
Views
Replies
Total Likes
Hi @idyllopus
AEM OOTB does not provide bookmarking features for end users.
However you can build a custom solution(A servlet) to save bookmark page to user profile/CUG user.
Or you can build something outside AEM, A database which allow to store and retrieve bookmarks and an API to interact.
Views
Replies
Total Likes
Thanks @arunpatidar -- are there existing tools you would recommend that handle similar tasks?
Views
Replies
Total Likes
In our case we did not used the AEM to store the dada because of multi publish setup and then syncing would be needed. So we created a spingboot app and integrated directly from frontend.
but you can rely on AWS Lambda/Adobe IO to run a simple program which does read/write user's bookmark, saved into your database.
Views
Replies
Total Likes
Hi @idyllopus,
As @arunpatidar said saving personalized content is not available OOTB in AEM, but it can be implemented with a few common patterns.
I would recommend to go with Custom User Content Storage in JCR or External DB
/home/users/<user-id>/...
in JCRAEM lets you store user-specific data under the user’s home folder in the repository.
Example:/home/users/s/santosh/save-list/page1
/home/users/s/santosh/save-list/page2
You can create a custom servlet or Sling model to:
Save a page reference (eg. cq:Page
path, title, thumbnail)
Retrieve it for the user's profile page
Requires login via AEM’s Adobe Granite OAuth or SAML/Auth handler.
This is useful if you're on AEM as a Cloud Service and want to avoid storing user-generated data in JCR (not recommended for scale).
Create an API microservice to handle save/retrieve actions.
Can use Adobe I/O Runtime or your own serverless backend.
I remember - AEM used to have Communities (social features like user profiles, blogs, etc.) but they’re deprecated.
Hi @idyllopus ,
You can achieve this by storing user-specific data in:
- JCR under /home/users/<uid> (good for AEM Sites / enterprise intranets)
- External DB/Cloud (recommended for Cloud Service / scale)
Below is a proven JCR-based working approach for AEM 6.5.
1. Structure for Saved Pages
Each user in AEM has a node under /home/users/.
We'll store saved bookmarks like:
/home/users/a/amit-user/save-list/page-<hash>
- jcr:title: "Page Title"
- pagePath: "/content/mysite/en/news/article-1"
- savedAt: "2025-06-13T12:33:55"
2. Bookmark Save/Remove Servlet
@Component(service = Servlet.class,
property = {
"sling.servlet.methods=POST",
"sling.servlet.paths=/bin/user/bookmark"
})
public class BookmarkServlet extends SlingAllMethodsServlet {
@Reference
private ResourceResolverFactory resolverFactory;
@Override
protected void doPost(SlingHttpServletRequest request, SlingHttpServletResponse response) throws IOException {
String action = request.getParameter("action"); // "add" or "remove"
String pagePath = request.getParameter("pagePath");
Session session = request.getResourceResolver().adaptTo(Session.class);
String userId = session.getUserID();
String bookmarkNodeName = "page-" + Integer.toHexString(pagePath.hashCode());
try {
Node userNode = session.getNode("/home/users/" + userId.substring(0, 1) + "/" + userId);
Node saveListNode = userNode.hasNode("save-list")
? userNode.getNode("save-list")
: userNode.addNode("save-list", "nt:unstructured");
if ("add".equals(action)) {
Node bookmarkNode = saveListNode.hasNode(bookmarkNodeName)
? saveListNode.getNode(bookmarkNodeName)
: saveListNode.addNode(bookmarkNodeName, "nt:unstructured");
bookmarkNode.setProperty("pagePath", pagePath);
bookmarkNode.setProperty("savedAt", Calendar.getInstance());
bookmarkNode.setProperty("jcr:title", request.getParameter("title"));
} else if ("remove".equals(action) && saveListNode.hasNode(bookmarkNodeName)) {
saveListNode.getNode(bookmarkNodeName).remove();
}
session.save();
response.setStatus(200);
} catch (Exception e) {
response.setStatus(500);
response.getWriter().write("Error: " + e.getMessage());
}
}
}
3. Front-End Button (HTL + JS)
HTL
<button class="bookmark-btn" data-path="${currentPage.path}" data-title="${currentPage.title}">
Save to My List
</button>
JS
document.querySelectorAll('.bookmark-btn').forEach(button => {
button.addEventListener('click', () => {
const pagePath = button.dataset.path;
const title = button.dataset.title;
fetch('/bin/user/bookmark', {
method: 'POST',
body: new URLSearchParams({
action: 'add',
pagePath,
title
}),
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
}).then(resp => {
if (resp.ok) {
alert("Saved!");
}
});
});
});
4. Read Saved Pages for Profile Page
Create a Sling Model or use JCR query to fetch all child nodes under:
/home/users/<first-letter>/<user-id>/save-list
Each node contains:
- pagePath
- jcr:title
- savedAt
Render them as links on the user profile page.
Note:
Only allow logged-in users (validate session).
Protect /bin/user/bookmark servlet via Apache Sling Referrer Filter or CSRF filter.
Set rep:policy ACL to allow users to modify their own save-list.
Use hash of page path to uniquely name saved nodes.
For AEM Cloud Service, use Adobe I/O + Firestore/DynamoDB instead.
Regards,
Amit
Views
Replies
Total Likes
Views
Likes
Replies
Views
Likes
Replies