diff --git a/Component Plug-in (CP) Examples/Client API-Enabled Examples/newsLoader/componentPlugin/appian-component-plugin.xml b/Component Plug-in (CP) Examples/Client API-Enabled Examples/newsLoader/componentPlugin/appian-component-plugin.xml
new file mode 100644
index 0000000..bb9c850
--- /dev/null
+++ b/Component Plug-in (CP) Examples/Client API-Enabled Examples/newsLoader/componentPlugin/appian-component-plugin.xml
@@ -0,0 +1,75 @@
+
+
+
+ Loads latest News
+
+ 1.0.0
+
+
+
+ 2.0.0
+ chrome firefox ie11 edge safari mobile
+ image.svg
+ index.html
+
+ connectedSystem
+ input-only
+ ConnectedSystem
+ connectedSystem field is required.
+
+
+ saveNewsToFolder
+ input-only
+ TypedValue
+ hidden
+ saveNewsToFolder field is required.
+
+
+ userDetails
+ input-only
+ Dictionary
+
+ a!map(
+ "name": user(loggedInUser(), "firstName"),
+ "email": user(loggedInUser(), "email"),
+ "country": user(loggedInUser(), "country")
+ )
+
+
+ {
+ "name": "Dummy Name",
+ "email": "dummy@email.com",
+ "country": "dummyCountry"
+ }
+
+
+
+ canAddNews
+ input-only
+ Boolean
+
+ a!isUserMemberOfGroup(loggedInUser(), "editors")
+
+ visible
+
+
+ newsFreshness
+ input-only
+
+
+ latest
+ oneWeek
+ twoWeeks
+ oneMonth
+
+
+ "latest"
+
+
+ sortBy
+ input-only
+ Text
+ "latest-first"
+
+
+
diff --git a/Component Plug-in (CP) Examples/Client API-Enabled Examples/newsLoader/componentPlugin/newsLoader/v1/app.js b/Component Plug-in (CP) Examples/Client API-Enabled Examples/newsLoader/componentPlugin/newsLoader/v1/app.js
new file mode 100644
index 0000000..bfc25ce
--- /dev/null
+++ b/Component Plug-in (CP) Examples/Client API-Enabled Examples/newsLoader/componentPlugin/newsLoader/v1/app.js
@@ -0,0 +1,266 @@
+let NewsLoadFriendlyName = "LoadNewsClientApi";
+let NewsStoreFriendlyName = "StoreNewsClientApi";
+const newsStructure = ["id", "title", "source", "date", "region", "content"];
+
+state = {
+ userDetails: {},
+ news: [],
+ region: null,
+ sortBy: null,
+ newsFreshness: null,
+ canAddNews: null,
+ connectedSystem: null
+};
+
+Appian.Component.onNewValue(async (newValues) => {
+ state.userDetails = newValues.userDetails;
+ state.region = newValues.region;
+ state.sortBy = newValues.sortBy;
+ state.newsFreshness = newValues.newsFreshness;
+ state.canAddNews = newValues.canAddNews;
+ state.connectedSystem = newValues.connectedSystem;
+
+ state.news = await fetchNews(newValues.connectedSystem);
+ render();
+ attachEventListeners();
+})
+
+// Render app
+function render() {
+ const app = document.getElementById('app');
+ app.innerHTML = `
+
+
+
+
+
+ Fetching News for you
+
+
+ ${state.canAddNews != null && state.canAddNews === true ? '' : ''}
+
+
+ ${
+ state.news?.length == 0 ?
+ `
+ no news to show
+
` :
+ `
+ ${
+ getFilteredNews().map(news =>`
+
+
+ ${news.title}
+ ${news.content}
+
+
+ `).join('')
+ }
+
`
+ }
+
+
+
+ `;
+}
+
+// Filter and sort news
+function getFilteredNews() {
+ let filtered = [];
+ for (let news of state.news) {
+ try {
+ let parsedNews = JSON.parse(news);
+
+ if (
+ parsedNews &&
+ typeof parsedNews === "object" &&
+ !Array.isArray(parsedNews) &&
+ isValidNews(parsedNews)
+ ) {
+ filtered.push(parsedNews);
+ }
+ } catch (e) {
+ console.error("Invalid JSON:", e);
+ }
+ }
+
+ // Filter by freshness
+ const now = new Date('2026-01-28');
+ filtered = filtered.filter(news => {
+ const newsDate = new Date(news.date);
+ const daysDiff = Math.floor((now - newsDate) / (1000 * 60 * 60 * 24));
+
+ switch(state.newsFreshness) {
+ case 'latest': return daysDiff <= 3;
+ case 'oneWeek': return daysDiff <= 7;
+ case 'twoWeeks': return daysDiff <= 14;
+ case 'oneMonth': return daysDiff <= 30;
+ default: return true;
+ }
+ });
+
+ // Sort
+ filtered.sort((a, b) => {
+ const dateA = new Date(a.date);
+ const dateB = new Date(b.date);
+ return state.sortBy === 'latest-first' ? dateB - dateA : dateA - dateB;
+ });
+
+ return filtered;
+}
+
+// Format date
+function formatDate(dateStr) {
+ const date = new Date(dateStr);
+ const now = new Date();
+ const daysDiff = Math.floor((now - date) / (1000 * 60 * 60 * 24));
+
+ if (daysDiff === 0) return 'Today';
+ if (daysDiff === 1) return 'Yesterday';
+ if (daysDiff < 7) return `${daysDiff} days ago`;
+ return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' });
+}
+
+// Event listeners
+function attachEventListeners() {
+ const addBtn = document.getElementById('addNewsBtn');
+ const modal = document.getElementById('modal');
+ const closeModal = document.getElementById('closeModal');
+ const cancelBtn = document.getElementById('cancelBtn');
+ const newsForm = document.getElementById('newsForm');
+ const newsFetch = document.getElementById('fetchNews');
+
+ if (addBtn) {
+ addBtn.addEventListener('click', () => {
+ modal.style.display = 'flex';
+ });
+ }
+
+ if (closeModal) {
+ closeModal.addEventListener('click', () => {
+ modal.style.display = 'none';
+ newsForm.reset();
+ });
+ }
+
+ if (cancelBtn) {
+ cancelBtn.addEventListener('click', () => {
+ modal.style.display = 'none';
+ newsForm.reset();
+ });
+ }
+
+ if (newsForm) {
+ newsForm.addEventListener('submit', async (e) => {
+ e.preventDefault();
+
+ const newArticle = {
+ id: state.news?.length + 1,
+ title: document.getElementById('newsTitle').value,
+ content: document.getElementById('newsContent').value,
+ source: document.getElementById('newsSource').value,
+ region: document.getElementById('newsRegion').value,
+ date: new Date().toISOString().split('T')[0]
+ };
+
+ await storeNews(newArticle);
+ window.location.reload();
+ });
+ }
+
+ if(newsFetch){
+ newsFetch.addEventListener('click', () => {
+ fetchNews(state.connectedSystem);
+ })
+ }
+
+ modal?.addEventListener('click', (e) => {
+ if (e.target === modal) {
+ modal.style.display = 'none';
+ newsForm.reset();
+ }
+ });
+}
+
+function isValidNews(news) {
+ return newsStructure.every(key =>
+ Object.prototype.hasOwnProperty.call(news, key)
+ );
+}
+
+async function storeNews(params) {
+ var connectedSystem = state.connectedSystem;
+ var payload = {
+ "news": JSON.stringify(params)
+ };
+ if(connectedSystem){
+ const resp = await Appian.Component.invokeClientApi(connectedSystem, NewsStoreFriendlyName, payload, ["canAddNews", "saveNewsToFolder"]);
+ return resp;
+ }
+}
+
+async function fetchNews() {
+ var connectedSystem = state.connectedSystem;
+ if(connectedSystem){
+ const resp = await Appian.Component.invokeClientApi(connectedSystem, NewsLoadFriendlyName, {}, ["saveNewsToFolder"]);
+
+ if(resp.type === "INVOCATION_SUCCESS" && Object.prototype.hasOwnProperty.call(resp.payload, "news")){
+ return resp.payload.news;
+ } else {
+ return [];
+ }
+
+ } else {
+ console.error("Connected system not provided")
+ return [];
+ }
+}
diff --git a/Component Plug-in (CP) Examples/Client API-Enabled Examples/newsLoader/componentPlugin/newsLoader/v1/image.svg b/Component Plug-in (CP) Examples/Client API-Enabled Examples/newsLoader/componentPlugin/newsLoader/v1/image.svg
new file mode 100644
index 0000000..c9716c9
--- /dev/null
+++ b/Component Plug-in (CP) Examples/Client API-Enabled Examples/newsLoader/componentPlugin/newsLoader/v1/image.svg
@@ -0,0 +1,13 @@
+
+
\ No newline at end of file
diff --git a/Component Plug-in (CP) Examples/Client API-Enabled Examples/newsLoader/componentPlugin/newsLoader/v1/index.html b/Component Plug-in (CP) Examples/Client API-Enabled Examples/newsLoader/componentPlugin/newsLoader/v1/index.html
new file mode 100644
index 0000000..2d0d2a1
--- /dev/null
+++ b/Component Plug-in (CP) Examples/Client API-Enabled Examples/newsLoader/componentPlugin/newsLoader/v1/index.html
@@ -0,0 +1,14 @@
+
+
+
+
+
+ NewsLoader
+
+
+
+
+
+
+
+
diff --git a/Component Plug-in (CP) Examples/Client API-Enabled Examples/newsLoader/componentPlugin/newsLoader/v1/newsLoader_en_US.properties b/Component Plug-in (CP) Examples/Client API-Enabled Examples/newsLoader/componentPlugin/newsLoader/v1/newsLoader_en_US.properties
new file mode 100644
index 0000000..c709a27
--- /dev/null
+++ b/Component Plug-in (CP) Examples/Client API-Enabled Examples/newsLoader/componentPlugin/newsLoader/v1/newsLoader_en_US.properties
@@ -0,0 +1,16 @@
+name=newsLoader
+description=A mock news loader component that will load news.
+parameter.userDetails.name=userDetails
+parameter.userDetails.description=User details as map that contains user_email, user_name, user_country keys.
+parameter.canAddNews.name=addNewNews
+parameter.canAddNews.description=Allow to add new news.
+parameter.newsFreshness.name=newsFreshness
+parameter.newsFreshness.description=Determines freshness of News.
+parameter.sortBy.name=sortBy
+parameter.sortBy.description=Order in which news will be shown.
+parameter.region.name=region
+parameter.region.description=News of whole region.
+parameter.connectedSystem.name=connectedSystem
+parameter.connectedSystem.description=Connected system constant for client API calls.
+parameter.saveNewsToFolder.name=saveNewsToFolder
+parameter.saveNewsToFolder.description=folder location where news would be saved.
\ No newline at end of file
diff --git a/Component Plug-in (CP) Examples/Client API-Enabled Examples/newsLoader/componentPlugin/newsLoader/v1/styles.css b/Component Plug-in (CP) Examples/Client API-Enabled Examples/newsLoader/componentPlugin/newsLoader/v1/styles.css
new file mode 100644
index 0000000..8d209a7
--- /dev/null
+++ b/Component Plug-in (CP) Examples/Client API-Enabled Examples/newsLoader/componentPlugin/newsLoader/v1/styles.css
@@ -0,0 +1,287 @@
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
+ background: #f8fafc;
+ min-height: 600px;
+ padding: 20px;
+}
+
+.container {
+ max-width: 1200px;
+ margin: 0 auto;
+}
+
+.header {
+ background: white;
+ padding: 30px;
+ border-radius: 12px;
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+ margin-bottom: 30px;
+ border: 1px solid #e2e8f0;
+}
+
+.user-info h1 {
+ font-size: 28px;
+ color: #0f172a;
+ margin-bottom: 8px;
+}
+
+.user-details {
+ color: #64748b;
+ font-size: 14px;
+}
+
+.controls {
+ background: white;
+ padding: 20px;
+ border-radius: 12px;
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+ margin-bottom: 30px;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ flex-wrap: wrap;
+ gap: 15px;
+ border: 1px solid #e2e8f0;
+}
+
+
+.btn-primary {
+ background: #3b82f6;
+ color: white;
+ border: none;
+ padding: 12px 24px;
+ border-radius: 8px;
+ font-size: 14px;
+ font-weight: 600;
+ cursor: pointer;
+ transition: all 0.2s;
+}
+
+.btn-primary:hover {
+ background: #2563eb;
+ box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3);
+}
+
+.news-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
+ gap: 24px;
+}
+
+.news-card {
+ background: white;
+ border-radius: 12px;
+ padding: 24px;
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+ transition: all 0.2s;
+ display: flex;
+ flex-direction: column;
+ border: 1px solid #e2e8f0;
+}
+
+.news-card:hover {
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+ border-color: #cbd5e0;
+}
+
+.news-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 12px;
+}
+
+.news-region {
+ background: #3b82f6;
+ color: white;
+ padding: 4px 12px;
+ border-radius: 20px;
+ font-size: 11px;
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+}
+
+.news-date {
+ color: #94a3b8;
+ font-size: 12px;
+ font-weight: 500;
+}
+
+.news-title {
+ font-size: 18px;
+ color: #0f172a;
+ margin-bottom: 12px;
+ line-height: 1.4;
+}
+
+.news-content {
+ color: #475569;
+ font-size: 14px;
+ line-height: 1.6;
+ margin-bottom: 16px;
+ flex-grow: 1;
+}
+
+.news-footer {
+ padding-top: 12px;
+ border-top: 1px solid #e2e8f0;
+}
+
+.news-source {
+ color: #64748b;
+ font-size: 13px;
+ font-weight: 600;
+}
+
+@media (max-width: 768px) {
+ .news-grid {
+ grid-template-columns: 1fr;
+ }
+
+ .controls {
+ flex-direction: column;
+ align-items: stretch;
+ }
+
+ .filters {
+ flex-direction: column;
+ }
+
+ .filter-group select {
+ width: 100%;
+ }
+}
+
+.modal {
+ display: none;
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background: rgba(0, 0, 0, 0.5);
+ align-items: center;
+ justify-content: center;
+ z-index: 1000;
+}
+
+.modal-content {
+ background: white;
+ border-radius: 12px;
+ width: 90%;
+ max-width: 600px;
+ box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1);
+}
+
+.modal-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 24px;
+ border-bottom: 1px solid #e2e8f0;
+}
+
+.modal-header h2 {
+ font-size: 20px;
+ color: #0f172a;
+}
+
+.close-btn {
+ background: none;
+ border: none;
+ font-size: 28px;
+ color: #94a3b8;
+ cursor: pointer;
+ line-height: 1;
+}
+
+.close-btn:hover {
+ color: #475569;
+}
+
+#newsForm {
+ padding: 24px;
+}
+
+.form-group {
+ margin-bottom: 20px;
+}
+
+.form-group label {
+ display: block;
+ font-size: 14px;
+ font-weight: 600;
+ color: #475569;
+ margin-bottom: 8px;
+}
+
+.form-group input,
+.form-group textarea,
+.form-group select {
+ width: 100%;
+ padding: 10px 14px;
+ border: 1px solid #cbd5e0;
+ border-radius: 8px;
+ font-size: 14px;
+ color: #0f172a;
+ font-family: inherit;
+}
+
+.form-group input:focus,
+.form-group textarea:focus,
+.form-group select:focus {
+ outline: none;
+ border-color: #3b82f6;
+ box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
+}
+
+.form-row {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 16px;
+}
+
+.form-actions {
+ display: flex;
+ justify-content: flex-end;
+ gap: 12px;
+ margin-top: 24px;
+}
+
+.btn-secondary {
+ background: white;
+ color: #475569;
+ border: 1px solid #cbd5e0;
+ padding: 12px 24px;
+ border-radius: 8px;
+ font-size: 14px;
+ font-weight: 600;
+ cursor: pointer;
+}
+
+.btn-secondary:hover {
+ background: #f8fafc;
+}
+
+.empty-state {
+ background: white;
+ border-radius: 12px;
+ padding: 60px 24px;
+ text-align: center;
+ color: #64748b;
+ font-size: 16px;
+ border: 1px solid #e2e8f0;
+}
+
+.no-news{
+ width: 100%;
+ text-align: center;
+ color: #475569;
+}
\ No newline at end of file
diff --git a/Component Plug-in (CP) Examples/Client API-Enabled Examples/newsLoader/connectedSystemPlugin/README.md b/Component Plug-in (CP) Examples/Client API-Enabled Examples/newsLoader/connectedSystemPlugin/README.md
new file mode 100644
index 0000000..1206298
--- /dev/null
+++ b/Component Plug-in (CP) Examples/Client API-Enabled Examples/newsLoader/connectedSystemPlugin/README.md
@@ -0,0 +1,42 @@
+## How to install
+The Component Plug-in and the Connected System Plug-in are packaged in two separate bundles. Installing both requires creating both bundles and then placing both into the plug-in directory separately.
+
+#### Installing the Connected System Plug-in
+* Enter the `connectedSystemPlugin` directory
+* Make sure you have `lib/appian-plug-in-sdk.jar`
+* Run the gradle JAR task (typically `./gradlew build`)
+* Drop the generated jar (which will be located in `build/libs/`) into the plugin directory of your Appian install `/_admin/plugins`
+
+#### Installing the Component Plug-in
+* See https://github.com/appian/integration-sdk-examples/blob/master/Component%20Plug-in%20(CP)%20Examples/README.md for installation instructions for the Component Plug-in
+
+## Sample interface
+```
+newsLoader(
+ label: "newsLoader",
+ labelPosition: "ABOVE",
+ validations: {},
+ height: "AUTO",
+ connectedSystem: null,
+ saveNewsToFolder: null,
+ userDetails: a!map(
+ "name": user(
+ loggedInUser(),
+ "firstName"
+ ),
+ "email": user(
+ loggedInUser(),
+ "email"
+ ),
+ "country": user(
+ loggedInUser(),
+ "country"
+ )
+ ),
+ canAddNews: a!isUserMemberOfGroup(
+ loggedInUser(),
+ "editors"
+ ),
+ newsFreshness: "latest"
+)
+```
diff --git a/Component Plug-in (CP) Examples/Client API-Enabled Examples/newsLoader/connectedSystemPlugin/build.gradle b/Component Plug-in (CP) Examples/Client API-Enabled Examples/newsLoader/connectedSystemPlugin/build.gradle
new file mode 100644
index 0000000..a5b9952
--- /dev/null
+++ b/Component Plug-in (CP) Examples/Client API-Enabled Examples/newsLoader/connectedSystemPlugin/build.gradle
@@ -0,0 +1,24 @@
+plugins {
+ id 'java'
+}
+
+group = 'org.mycorp'
+version = '1.0-SNAPSHOT'
+
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+ compileOnly 'com.appian:connected-systems-core:1.10.0'
+ implementation 'com.appian:connected-systems-client:1.4.0'
+ testImplementation platform('org.junit:junit-bom:5.10.0')
+ testImplementation 'org.junit.jupiter:junit-jupiter'
+
+ // Appian's suite API jar
+ compileOnly files('lib/appian-plug-in-sdk.jar')
+}
+
+test {
+ useJUnitPlatform()
+}
diff --git a/Component Plug-in (CP) Examples/Client API-Enabled Examples/newsLoader/connectedSystemPlugin/settings.gradle b/Component Plug-in (CP) Examples/Client API-Enabled Examples/newsLoader/connectedSystemPlugin/settings.gradle
new file mode 100644
index 0000000..a83ef93
--- /dev/null
+++ b/Component Plug-in (CP) Examples/Client API-Enabled Examples/newsLoader/connectedSystemPlugin/settings.gradle
@@ -0,0 +1,2 @@
+rootProject.name = 'news-loader'
+
diff --git a/Component Plug-in (CP) Examples/Client API-Enabled Examples/newsLoader/connectedSystemPlugin/src/main/java/com/mycorp/newsloader/templates/LoadNewsFromFolderClientAPI.java b/Component Plug-in (CP) Examples/Client API-Enabled Examples/newsLoader/connectedSystemPlugin/src/main/java/com/mycorp/newsloader/templates/LoadNewsFromFolderClientAPI.java
new file mode 100644
index 0000000..7774068
--- /dev/null
+++ b/Component Plug-in (CP) Examples/Client API-Enabled Examples/newsLoader/connectedSystemPlugin/src/main/java/com/mycorp/newsloader/templates/LoadNewsFromFolderClientAPI.java
@@ -0,0 +1,84 @@
+package com.mycorp.newsloader.templates;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import com.appian.connectedsystems.simplified.sdk.SimpleClientApi;
+import com.appian.connectedsystems.simplified.sdk.SimpleClientApiRequest;
+import com.appian.connectedsystems.templateframework.sdk.ClientApiResponse;
+import com.appian.connectedsystems.templateframework.sdk.ExecutionContext;
+import com.appian.connectedsystems.templateframework.sdk.TemplateId;
+import com.appiancorp.suiteapi.common.exceptions.AppianStorageException;
+import com.appiancorp.suiteapi.content.ContentFilter;
+import com.appiancorp.suiteapi.content.ContentService;
+import com.appiancorp.suiteapi.content.DocumentInputStream;
+import com.appiancorp.suiteapi.content.exceptions.InvalidContentException;
+import com.appiancorp.suiteapi.content.exceptions.InvalidTypeMaskException;
+import com.appiancorp.suiteapi.type.TypedValue;
+import com.appiancorp.type.AppianTypeLong;
+
+/**
+ * This is an example of a Client API that performs an operation when executed from a
+ * Component Plug-in (CP). The Client API accepts a data structure which contains
+ * the CP request payload as well as the data stored inside the Connected System object.
+ * It uses both pieces to perform the operation.
+ *
+ * In this example, Client API gets folder reference (TypedValue) for the request payload
+ * and return all the news stored in the files in that folder.
+ */
+
+@TemplateId(name = "LoadNewsClientApi")
+public class LoadNewsFromFolderClientAPI extends SimpleClientApi {
+
+ private final ContentService contentService;
+
+ public LoadNewsFromFolderClientAPI(ContentService contentService){
+ this.contentService = contentService;
+ }
+
+ @Override
+ protected ClientApiResponse execute(SimpleClientApiRequest simpleClientApiRequest, ExecutionContext executionContext){
+
+ // Folder reference provided in the request's secured payload.
+ TypedValue folderCons = (TypedValue)simpleClientApiRequest.getSecuredPayload().getOrDefault("saveNewsToFolder", new TypedValue());
+ if(folderCons.getValue() == null){
+ throw new IllegalStateException("saveNewsToFolder should be null.");
+ }
+
+ if(!AppianTypeLong.FOLDER.equals(folderCons.getTypeRef().getId())){
+ throw new IllegalStateException("saveNewsToFolder should be set to a constant pointing to a folder.");
+ }
+
+ Long folderId = (Long) folderCons.getValue();
+ List newsJson = new ArrayList<>();
+
+ try {
+ // Getting all the files IDs stored in that folder
+ Long[] docIds = contentService.getAllChildrenIds(folderId, ContentFilter.DOCUMENTS, 0);
+
+ for(Long id: docIds){
+ // Reading file content via file ID.
+ DocumentInputStream docStream = contentService.getDocumentInputStream(id);
+
+ try(BufferedReader reader = new BufferedReader(new InputStreamReader(docStream, StandardCharsets.UTF_8))) {
+ String news = reader.lines().collect(Collectors.joining("\n"));
+ newsJson.add(news);
+ } catch ( IOException e) {
+ throw new IllegalStateException("Cannot read files in saveNewsToFolder's folder.");
+ }
+ }
+
+ } catch (InvalidContentException | InvalidTypeMaskException | AppianStorageException e) {
+ throw new RuntimeException(e);
+ }
+ return new ClientApiResponse(Map.of(
+ "news", newsJson
+ ));
+ }
+}
diff --git a/Component Plug-in (CP) Examples/Client API-Enabled Examples/newsLoader/connectedSystemPlugin/src/main/java/com/mycorp/newsloader/templates/NewsLoaderConnectedSystemTemplate.java b/Component Plug-in (CP) Examples/Client API-Enabled Examples/newsLoader/connectedSystemPlugin/src/main/java/com/mycorp/newsloader/templates/NewsLoaderConnectedSystemTemplate.java
new file mode 100644
index 0000000..2b2f1c2
--- /dev/null
+++ b/Component Plug-in (CP) Examples/Client API-Enabled Examples/newsLoader/connectedSystemPlugin/src/main/java/com/mycorp/newsloader/templates/NewsLoaderConnectedSystemTemplate.java
@@ -0,0 +1,39 @@
+package com.mycorp.newsloader.templates;
+
+import com.appian.connectedsystems.simplified.sdk.configuration.SimpleConfiguration;
+import com.appian.connectedsystems.simplified.sdk.connectiontesting.SimpleTestableConnectedSystemTemplate;
+import com.appian.connectedsystems.templateframework.sdk.ExecutionContext;
+import com.appian.connectedsystems.templateframework.sdk.TemplateId;
+import com.appian.connectedsystems.templateframework.sdk.connectiontesting.TestConnectionResult;
+
+/**
+ * This Connected System template serves as a secure vault for mock credentials
+ * associated with a hypothetical third-party News API.
+ *
+ * NOTE: This configuration is for demonstration purposes only. It does not
+ * influence active Client API transactions, but rather illustrates best
+ * practices for centralized credential management within Connected Systems.
+ */
+
+@TemplateId(name = "NewsLoaderConnectedSystemTemplate")
+public class NewsLoaderConnectedSystemTemplate extends SimpleTestableConnectedSystemTemplate {
+ public static final String MOCK_NEWS_CORP_PASSWORD= "mockNewsCorpPassword";
+
+ @Override
+ protected SimpleConfiguration getConfiguration(
+ SimpleConfiguration simpleConfiguration, ExecutionContext executionContext) {
+ return simpleConfiguration.setProperties(
+ textProperty(MOCK_NEWS_CORP_PASSWORD)
+ .label("Password for Mock_News_Corp authentication")
+ .instructionText("Just a mock simulation of 3rd party API call.")
+ .isRequired(true)
+ .isImportCustomizable(true)
+ .build()
+ );
+ }
+
+ @Override
+ protected TestConnectionResult testConnection(SimpleConfiguration configuration, ExecutionContext executionContext) {
+ return TestConnectionResult.success();
+ }
+}
diff --git a/Component Plug-in (CP) Examples/Client API-Enabled Examples/newsLoader/connectedSystemPlugin/src/main/java/com/mycorp/newsloader/templates/StoreNewsToFolderClientAPI.java b/Component Plug-in (CP) Examples/Client API-Enabled Examples/newsLoader/connectedSystemPlugin/src/main/java/com/mycorp/newsloader/templates/StoreNewsToFolderClientAPI.java
new file mode 100644
index 0000000..213ee8a
--- /dev/null
+++ b/Component Plug-in (CP) Examples/Client API-Enabled Examples/newsLoader/connectedSystemPlugin/src/main/java/com/mycorp/newsloader/templates/StoreNewsToFolderClientAPI.java
@@ -0,0 +1,79 @@
+package com.mycorp.newsloader.templates;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Map;
+import java.util.UUID;
+
+import com.appian.connectedsystems.simplified.sdk.SimpleClientApi;
+import com.appian.connectedsystems.simplified.sdk.SimpleClientApiRequest;
+import com.appian.connectedsystems.templateframework.sdk.ClientApiResponse;
+import com.appian.connectedsystems.templateframework.sdk.ExecutionContext;
+import com.appian.connectedsystems.templateframework.sdk.TemplateId;
+import com.appiancorp.suiteapi.content.ContentConstants;
+import com.appiancorp.suiteapi.content.ContentService;
+import com.appiancorp.suiteapi.content.ContentUploadOutputStream;
+import com.appiancorp.suiteapi.knowledge.Document;
+import com.appiancorp.suiteapi.type.TypedValue;
+import com.appiancorp.type.AppianTypeLong;
+
+/**
+ * This is an example of a Client API that performs an operation when executed from a
+ * Component Plug-in (CP). The Client API accepts a data structure which contains
+ * the CP request payload as well as the data stored inside the Connected System object.
+ * It uses both pieces to perform the operation.
+ *
+ * In this example, Client API will reviece news and store it as file in the defined folder
+ * Returns ID of newly stored news file
+ */
+
+@TemplateId(name="StoreNewsClientApi")
+public class StoreNewsToFolderClientAPI extends SimpleClientApi {
+ private final ContentService contentService;
+
+ public StoreNewsToFolderClientAPI(ContentService contentService){
+ this.contentService = contentService;
+ }
+ @Override
+ protected ClientApiResponse execute(SimpleClientApiRequest simpleClientApiRequest, ExecutionContext executionContext) {
+
+ // News payload
+ String newsPayload = (String)simpleClientApiRequest.getPayload().getOrDefault("news", "{}");
+
+ // permission to add news (secured)
+ Boolean canAddNewNews = (Boolean)simpleClientApiRequest.getSecuredPayload().getOrDefault("canAddNews", false);
+
+ // folder to store news file (TypedValue)
+ TypedValue folderCons = (TypedValue)simpleClientApiRequest.getSecuredPayload().getOrDefault("saveNewsToFolder", new TypedValue());
+
+ if(folderCons.getValue() == null){
+ throw new IllegalStateException("saveNewsToFolder should be null.");
+ }
+
+ if(!AppianTypeLong.FOLDER.equals(folderCons.getTypeRef().getId())){
+ throw new IllegalStateException("saveNewsToFolder should be set to a constant pointing to a folder.");
+ }
+
+ if(canAddNewNews){
+ Document newsDoc = new Document();
+ newsDoc.setName(UUID.randomUUID().toString());
+ newsDoc.setExtension("json");
+ newsDoc.setParent((Long)folderCons.getValue());
+
+ // uploading news document to the defined folder
+ try (ContentUploadOutputStream cos = contentService.uploadDocument(newsDoc, ContentConstants.UNIQUE_NONE)) {
+ cos.write(newsPayload.getBytes(StandardCharsets.UTF_8));
+ Long newsDocId = cos.getContentId();
+
+ return new ClientApiResponse(
+ Map.of(
+ "newsDocId", newsDocId
+ )
+ );
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to store news to folder.");
+ }
+
+ }
+ throw new IllegalStateException("You don't have permission to add news.");
+ }
+}
diff --git a/Component Plug-in (CP) Examples/Client API-Enabled Examples/newsLoader/connectedSystemPlugin/src/main/resources/appian-plugin.xml b/Component Plug-in (CP) Examples/Client API-Enabled Examples/newsLoader/connectedSystemPlugin/src/main/resources/appian-plugin.xml
new file mode 100644
index 0000000..c7dc300
--- /dev/null
+++ b/Component Plug-in (CP) Examples/Client API-Enabled Examples/newsLoader/connectedSystemPlugin/src/main/resources/appian-plugin.xml
@@ -0,0 +1,14 @@
+
+
+
+ My Corp Connected System - News Loader
+
+ 1.0.0.0
+
+
+
+
+
+
+
+
diff --git a/Component Plug-in (CP) Examples/Client API-Enabled Examples/newsLoader/connectedSystemPlugin/src/main/resources/resources_en_US.properties b/Component Plug-in (CP) Examples/Client API-Enabled Examples/newsLoader/connectedSystemPlugin/src/main/resources/resources_en_US.properties
new file mode 100644
index 0000000..d005f81
--- /dev/null
+++ b/Component Plug-in (CP) Examples/Client API-Enabled Examples/newsLoader/connectedSystemPlugin/src/main/resources/resources_en_US.properties
@@ -0,0 +1,2 @@
+NewsLoaderConnectedSystemTemplate.name=News Loader Connected System Plug-in
+NewsLoaderConnectedSystemTemplate.description=News Loader CS for learning how to build Client APIs for CPs!