App that covers React + FDK capabilities on Platform 3.0
(Freshdesk + Freshservice)
Superstack ships working examples of every major Freshworks platform capability so you can clone, explore, and start building immediately.
What's included:
- 13 Freshdesk placeholder locations + minimal
service_ticket.ticket_sidebar(Freshservice install stub); modal and dialog templates - 37 request templates (Freshdesk CRUD, optional cross-product ticket templates
fs*/fd*for API Playground, mailboxes, email settings, external APIs) - Server Method Invocation (SMI) with two server functions; Instance communication demo on full page (
instance.get/send/receiveon the same Freshdesk page) - 7 event handlers (app lifecycle, ticket, contact, conversation)
- Custom React-based installation parameters page
- Redux state management, React Router, i18n (6 languages), Error Boundary
- Jest test suite with coverage
| Prerequisites | Quick Start | Development Commands |
| Project Structure | Frontend Stack | Placeholders |
| Request Templates | Server Functions (SMI) | Event Handlers |
| Instance Communication | Interface Methods | Data Storage |
| Installation Parameters | App Lifecycle |
| Testing | Contributing | License | Resources |
This app targets Freshworks Platform 3.0 (generated by FDK). Node.js and FDK versions expected for this repo are pinned in manifest.json under engines.
| Requirement | Version |
|---|---|
| Node.js | 24.11.0 (engines.node in manifest.json) |
| npm | 10.x or higher (ships with Node.js 24) |
| FDK (Freshworks Developer Kit) | 10.1.0 (engines.fdk in manifest.json) |
npm install https://cdn.freshdev.io/fdk/latest-v24.tgz -gVerify:
fdk versiongit clone https://github.com/freshworks-developers/superstack.git
cd superstack
npm installfdk runThe local dev server starts at http://*:10001/ (open http://localhost:10001 on your machine). Leave this process running while you test in the browser.
When fdk run is up, the CLI reminds you to append dev=true to your Freshdesk URL and points at helper URLs (installation page and event simulation).
Before dev=true binds local assets, tell the FDK which Freshdesk modules you use and your account URL.
- With
fdk runrunning, openhttp://localhost:10001/system_settingsin your browser. - Subscribe to the modules you plan to test:
common— full-page app, CTI global sidebar, shared requests, SMI, events.support_ticket— ticket sidebar, top navigation, attachment, conversation editor, backgrounds, etc.support_contact— contact sidebar and background.support_company— company background.
- Enter Organization domain and Enter account URL using your Freshdesk login URL (e.g.
https://yourcompany.freshdesk.com). - Click Continue. Complete
http://localhost:10001/custom_configs/ iparams if prompted (§4.1).
Reopen http://localhost:10001/system_settings if you change which surfaces you test.
Instance APIs in local dev: client.instance.get() only lists app instances that are active in the same browser tab (e.g. full page app open in Apps plus ticket sidebar on a ticket). Use Home → Instance communication after opening both surfaces with dev=true.
Use a Freshdesk account where you can install or sideload the app. Complete §3.5 Subscribe to modules with your Freshdesk URL and support_ticket / common as needed, then follow the steps below so installation parameters, request templates, and server methods run against a real product context (not just unit tests).
Request templates and many samples need your Freshdesk domain and API key from the installation parameters UI.
- With
fdk runrunning, open the local installation page:http://localhost:10001/custom_configs - Enter valid values (same as you would in the live app settings) and save so the dev app can call the Freshdesk API.
Alternatively, complete the install / settings flow inside Freshdesk once the app is in dev mode; the iparams page is the React surface under config/.
Add dev=true to whatever Freshdesk URL you already have open:
-
No query string yet — start the query with
?dev=true(works for a bare portal URL with no path after the domain, or for any path that does not already include?):https://yourcompany.freshdesk.com?dev=true https://yourcompany.freshdesk.com/a/tickets/1?dev=true -
URL already has
?…— append with&dev=true(do not add a second?):https://yourcompany.freshdesk.com/a/tickets/1?filter=new&openTicketSidebar=true&dev=true
After this, placeholders load from your machine at port 10001 instead of a published package.
| Where | What to exercise |
|---|---|
| Freshdesk · Apps (full page app) | Home: SMI (client.request.invoke), interface methods, data storage, app lifecycle demos. API Playground: Freshdesk templates, optional Cross-product (Freshservice) (fsGetTickets, fsCreateTicket, …) when Freshservice iparams are set, and external APIs. |
| Freshservice · Apps (full page app) | Same Home demos. API Playground: templates that use current_host for Freshservice where applicable, optional Cross-product (Freshdesk) (fdGetTickets, fdCreateTicket, …) when Freshdesk iparams are set, plus external APIs. |
| Freshdesk · Any ticket | Sidebar: SMI button (invokeFromClient), ticket-scoped UI. Top navigation, attachment, conversation editor, requester info, new-ticket requester info, background: additional invokeTemplate / platform examples. |
| Freshdesk · CTI global sidebar | Ticket list-style invokeTemplate calls (e.g. get/create ticket). |
| Freshdesk · Any contact | Contact sidebar and background placeholders. |
| Freshdesk · Any company | Company sidebar and background placeholders. |
Work through ticket and full page surfaces first if you are validating Freshdesk REST usage via templates; use Home → SMI and ticket sidebar → Call Server Method to verify serverless / SMI paths. For instance communication, open Apps (full page) and a ticket (sidebar) with dev=true, then use Home → Instance communication (send/receive between instances on the same page).
To trigger product, app setup, and external events without performing every action in the UI:
http://localhost:10001/web/test
Use this alongside real pages to confirm event handlers and lifecycle callbacks behave as expected.
| URL | Purpose |
|---|---|
http://localhost:10001/system_settings |
Modules + Freshdesk account URL (if dev=true does not attach) |
http://localhost:10001/custom_configs |
Installation parameters while developing |
http://localhost:10001/web/test |
Simulate serverless / product events |
No — you do not have to enable every module in system settings for every test run. On Freshdesk, select common plus the support_* modules you use. On Freshservice, select common and service_ticket (minimum for this repo to install there — see below).
full_page_app lives under modules.common (same index.html as Freshdesk). Freshservice still needs at least one service_ticket placeholder for the app to install — this manifest uses ticket_sidebar mapped to ticketSidebarService.html (minimal copy only; the ticket sidebar surface is not reliable on Freshservice).
fdk run, thenhttp://localhost:10001/system_settings: subscribecommonandservice_ticket, enter your Freshservice account URL (https://yourorg.freshservice.com), ITSM or MSP if asked, Continue, then savecustom_configs/ iparams if prompted.- Chrome: allow insecure content for your Freshservice origin so
http://localhost:10001can load in the iframe (Freshservice local testing). - Append
?dev=trueor&dev=trueto your Freshservice URL, open Apps in the product menu — you should see the full-page Superstack UI (index.html). - API Playground → Cross-product (Freshdesk) lists, fetches, and creates Freshdesk tickets via
iparam.freshdeskDomain+iparam.freshdeskApi(optional pair on the installation page), mirroringfs*on Freshdesk. - Use full page for the full Freshservice demo;
ticketSidebarService.htmlexists to satisfyservice_ticketonly (not a Freshdesk-style sidebar experience).
superstack/
├── app/ # Frontend
│ ├── index.html # Full Page App entry
│ ├── ticketSidebar.html # Freshdesk ticket sidebar (support_ticket)
│ ├── ticketSidebarService.html # Minimal Freshservice service_ticket.ticket_sidebar
│ ├── contactSidebar.html # Contact Sidebar entry
│ ├── topNavigation.html # Ticket Top Navigation entry
│ ├── ctiGlobalSidebar.html # CTI Global Sidebar entry
│ ├── ticketBackground.html # Ticket Background (no UI)
│ ├── timeEntryBackground.html # Time Entry Background (no UI)
│ ├── contactBackground.html # Contact Background (no UI)
│ ├── companyBackground.html # Company Background (no UI)
│ ├── ticketAttachment.html # Ticket Attachment entry
│ ├── ticketConversationEditor.html # Conversation Editor entry
│ ├── ticketRequesterInfo.html # Requester Info entry
│ ├── newTicketRequesterInfo.html # New Ticket Requester Info entry
│ ├── companySidebar.html # Company Sidebar entry
│ ├── modal.html # Modal template
│ ├── dialog.html # Dialog template
│ ├── icon.svg # App icon
│ │
│ ├── components/ # React components
│ │ ├── index.jsx # Entry point with Redux Provider
│ │ ├── App.jsx # Main app with React Router
│ │ ├── Home.jsx # Platform features demo
│ │ ├── ApiDemo.jsx # Request Templates playground
│ │ ├── Counter.jsx # Redux example
│ │ ├── Form.jsx # Crayons form + DB operations
│ │ ├── StockMarket.jsx # Live data example
│ │ ├── TailwindPage.jsx # Tailwind CSS demo
│ │ ├── MaterialuiAccordian.jsx # Material UI example
│ │ ├── I18nPage.jsx # Internationalization demo
│ │ ├── Modal.jsx # Modal component
│ │ ├── ClassComponent.jsx # Class component example
│ │ ├── ErrorBoundary.jsx # Error boundary wrapper
│ │ ├── context/ # React Context providers
│ │ └── placeholders/ # Placeholder-specific components
│ │ ├── PlaceholderWrapper.jsx # Reusable init wrapper
│ │ ├── ticketSidebar.jsx # Freshdesk ticket sidebar
│ │ ├── ticketSidebarService.jsx # Freshservice minimal ticket_sidebar
│ │ ├── topNavigation.jsx
│ │ ├── ctiGlobalSidebar.jsx
│ │ ├── contactSidebar.jsx
│ │ ├── companySidebar.jsx
│ │ ├── ticketBackground.jsx
│ │ ├── timeEntryBackground.jsx
│ │ ├── ticketAttachment.jsx
│ │ ├── ticketConversationEditor.jsx
│ │ ├── ticketRequesterInfo.jsx
│ │ └── newTicketRequesterInfo.jsx
│ │
│ ├── hooks/ # Custom React hooks
│ │ └── useFreshworksData.js # Freshworks data fetching hook
│ ├── reducer/ # Redux reducers
│ │ ├── counterReducer.js
│ │ └── ticketsReducer.js
│ ├── store/ # Redux store
│ │ └── index.js
│ ├── context/ # i18n context
│ │ └── I18nContext.jsx
│ ├── i18n.js # Translation utility
│ ├── locales/ # Translation files
│ │ ├── en.json
│ │ ├── es.json
│ │ ├── fr.json
│ │ ├── de.json
│ │ ├── ja.json
│ │ └── ar.json
│ ├── styles/
│ │ └── style.css
│ ├── icons/
│ │ └── image-one.svg
│ └── public/ # Static assets
│ ├── banner.svg
│ └── logos/
│
├── config/ # App configuration
│ ├── iparams.html # Custom iparam UI entry
│ ├── requests.json # Request template definitions
│ └── assets/
│ ├── components/
│ │ ├── main.jsx # Iparam React entry
│ │ └── Iparam.jsx # Iparam React component
│ └── style/
│ └── styles.css
│
├── server/ # Server-side code
│ └── server.js # SMI functions + event handlers
│
├── tests/ # Jest test suite
│ ├── App.test.js
│ ├── hooks/
│ │ └── useFreshworksData.test.js
│ └── reducers/
│ ├── counterReducer.test.js
│ └── ticketsReducer.test.js
│
├── manifest.json # Platform 3.0 manifest
├── package.json # Dependencies (npm)
├── jest.config.json # Jest configuration
├── tailwind.config.js # Tailwind CSS configuration
├── postcss.config.js # PostCSS configuration
├── setupTests.js # Jest setup
└── LICENSE.md # Freshworks-only License
Placeholders define where your app renders. Superstack includes 13 Freshdesk location entries in manifest.json plus service_ticket.ticket_sidebar on ticketSidebarService.html (minimal stub for Freshservice install). common (full page, CTI) applies to both products when the app is installed there.
| Placeholder | Module | HTML Entry | Description |
|---|---|---|---|
full_page_app |
common |
index.html |
Full page in the Apps menu |
cti_global_sidebar |
common |
ctiGlobalSidebar.html |
Global CTI sidebar |
ticket_top_navigation |
support_ticket |
topNavigation.html |
Top bar on ticket views |
ticket_sidebar |
support_ticket |
ticketSidebar.html |
Freshdesk ticket sidebar (full demo) |
ticket_sidebar |
service_ticket |
ticketSidebarService.html |
Freshservice install stub (full UI: full page) |
ticket_requester_info |
support_ticket |
ticketRequesterInfo.html |
Requester info section |
new_ticket_requester_info |
support_ticket |
newTicketRequesterInfo.html |
New ticket requester info |
ticket_attachment |
support_ticket |
ticketAttachment.html |
Attachment section |
ticket_conversation_editor |
support_ticket |
ticketConversationEditor.html |
Reply editor area |
ticket_background |
support_ticket |
ticketBackground.html |
Background (no UI) |
time_entry_background |
support_ticket |
timeEntryBackground.html |
Time Logs tab background |
contact_sidebar |
support_contact |
contactSidebar.html |
Contact detail sidebar |
contact_background |
support_contact |
contactBackground.html |
Contact page background |
company_background |
support_company |
companyBackground.html |
Company page background |
| Modal / Dialog | Interface | modal.html, dialog.html |
Triggered programmatically |
Most templates target Freshdesk (current_host.endpoint_urls.freshdesk). Optional fs* templates call Freshservice using iparam.freshserviceDomain and iparam.freshserviceApi (see Cross-product (Freshservice) below). Optional fd* templates call Freshdesk using iparam.freshdeskDomain and iparam.freshdeskApi while the app runs on Freshservice (see Cross-product (Freshdesk)).
Request templates are defined in config/requests.json and declared in manifest.json under modules.common.requests. They proxy HTTP calls through the platform so credentials never reach the browser.
These are not client.instance messages. They are separate HTTPS request templates to the Freshservice API while the app runs in Freshdesk:
| Template | Method | Purpose |
|---|---|---|
fsGetTickets |
GET | List tickets on the Freshservice account in iparams |
fsGetTicketById |
GET | Get one Freshservice ticket |
fsCreateTicket |
POST | Create a Freshservice ticket |
Configure Freshservice domain + API key on the installation page (optional pair). Use API Playground → Cross-product (Freshservice) to try them.
Symmetric fd* templates call the Freshdesk API using installation params only (no reliance on current_host.endpoint_urls.freshdesk):
| Template | Method | Purpose |
|---|---|---|
fdGetTickets |
GET | List tickets on the Freshdesk account in iparams |
fdGetTicketById |
GET | Get one Freshdesk ticket |
fdCreateTicket |
POST | Create a Freshdesk ticket |
Configure Freshdesk domain + API key on the installation page (optional pair). Use API Playground → Cross-product (Freshdesk) to try them.
| Template | Method | Endpoint |
|---|---|---|
getTickets |
GET | /api/v2/tickets |
getTicketById |
GET | /api/v2/tickets/{id} |
searchTickets |
GET | /api/v2/search/tickets |
createTicket |
POST | /api/v2/tickets |
updateTicket |
PUT | /api/v2/tickets/{id} |
deleteTicket |
DELETE | /api/v2/tickets/{id} |
getContacts |
GET | /api/v2/contacts |
getContactById |
GET | /api/v2/contacts/{id} |
searchContacts |
GET | /api/v2/search/contacts |
createContact |
POST | /api/v2/contacts |
updateContact |
PUT | /api/v2/contacts/{id} |
deleteContact |
DELETE | /api/v2/contacts/{id} |
getCompanies |
GET | /api/v2/companies |
getCompanyById |
GET | /api/v2/companies/{id} |
searchCompanies |
GET | /api/v2/search/companies |
createCompany |
POST | /api/v2/companies |
updateCompany |
PUT | /api/v2/companies/{id} |
deleteCompany |
DELETE | /api/v2/companies/{id} |
getMailboxes |
GET | /api/v2/email/mailboxes |
getMailboxById |
GET | /api/v2/email/mailboxes/{id} |
createMailbox |
POST | /api/v2/email/mailboxes |
updateMailbox |
PUT | /api/v2/email/mailboxes/{id} |
deleteMailbox |
DELETE | /api/v2/email/mailboxes/{id} |
getEmailSettings |
GET | /api/v2/email/settings |
updateEmailSettings |
PUT | /api/v2/email/settings |
getEmailBcc |
GET | /api/v2/notifications/email/bcc |
updateEmailBcc |
PUT | /api/v2/notifications/email/bcc |
secureApiCall |
GET | Custom domain + path |
| Template | Method | Host |
|---|---|---|
getJoke |
GET | icanhazdadjoke.com |
getProgrammingJoke |
GET | official-joke-api.appspot.com |
getChuckNorrisJoke |
GET | api.chucknorris.io |
getRandomUser |
GET | randomuser.me |
getWeatherByCity |
GET | api.openweathermap.org |
// Frontend: invoke a template at runtime
const response = await client.request.invokeTemplate("getTickets", {
context: { per_page: "10", page: "1" }
});
const tickets = JSON.parse(response.response);Server Method Invocation lets the frontend call server-side functions securely. Defined in server/server.js and declared in manifest.json under modules.common.functions.
| Function | Timeout | Purpose |
|---|---|---|
invokeFromClient |
15s | Generic server call from frontend |
serverMethod |
10s | Action-based dispatch (sync, analyze, etc.) |
// Frontend: call a server function
const result = await client.request.invoke("invokeFromClient", {
action: "test",
ticketId: 123
});Server-side handlers that fire automatically on Freshdesk events. Defined in server/server.js and declared in manifest.json under each module's events.
| Event | Module | Handler | Trigger |
|---|---|---|---|
onAppInstall |
common |
onAppInstallCallback |
App installed |
onAppUninstall |
common |
onAppUninstallCallback |
App uninstalled |
onTicketCreate |
support_ticket |
onTicketCreateCallback |
Ticket created |
onTicketUpdate |
support_ticket |
onTicketUpdateCallback |
Ticket updated |
onConversationCreate |
support_ticket |
onConversationCreateCallback |
Reply or note added |
onContactCreate |
support_contact |
onContactCreateCallback |
Contact created |
onContactUpdate |
support_contact |
onContactUpdateCallback |
Contact updated |
When your app runs in multiple placeholders simultaneously, instances can talk to each other.
// Get all running instances
const instances = await client.instance.get();
// Send data to a specific instance
await client.instance.send({
message: { action: "refresh" },
receiver: [targetInstanceId]
});
// Listen for messages
client.instance.receive((event) => {
const data = event.helper.getData();
});| Method | Description |
|---|---|
client.instance.get() |
List all active app instances |
client.instance.context() |
Get current instance context |
client.instance.send({ message, receiver }) |
Send data to other instances |
client.instance.receive(callback) |
Listen for incoming messages |
client.instance.resize({ height }) |
Resize current instance (max 700px) |
client.instance.close() |
Close modal or dialog |
client.instance.get(), send(), and receive() coordinate multiple app instances on the same Freshdesk page (e.g. full page app + ticket sidebar + modals). instance.get() only sees iframes active in that browser tab.
Local dev: Open Apps (full page) and a ticket (sidebar), both with dev=true. Then use Home → Instance communication (refresh list, send to selected, receive log).
Different browser tabs do not share one instance list — that is expected platform behaviour, not specific to fdk run.
Trigger UI actions from code.
// Notification
client.interface.trigger("showNotify", { type: "success", message: "Done!" });
// Modal
client.interface.trigger("showModal", {
title: "Details",
template: "modal.html",
data: { ticketId: 123 }
});
// Dialog
client.interface.trigger("showDialog", { title: "Info", template: "dialog.html" });
// Confirmation
const result = await client.interface.trigger("showConfirm", {
title: "Confirm",
message: "Are you sure?"
});
// Set ticket field
client.interface.trigger("setValue", { id: "priority", value: 2 });
// Navigate to ticket
client.interface.trigger("click", { id: "ticket", value: 123 });The platform provides a key-value store (up to 6 MB per key).
await client.db.set("config", { theme: "dark" });
const result = await client.db.get("config");
await client.db.delete("config");Superstack uses a custom React-based installation parameters page instead of JSON configuration.
config/
├── iparams.html # Entry point loaded by FDK
└── assets/
└── components/
├── main.jsx # React mount
└── Iparam.jsx # Custom form component
The Iparam.jsx component renders the settings form. FDK calls postConfigs() to collect values and validate() before saving.
// Access iparams in frontend
const iparams = await client.iparams.get();
// Access in server event handler
function onAppInstallCallback(payload) {
const domain = payload.iparams.freshdeskDomain;
}const client = await app.initialized();
client.events.on("app.activated", () => {
// App is visible -- fetch data here
});
client.events.on("app.deactivated", () => {
// App is hidden -- clean up here
});Dependency ranges are declared in package.json; the versions below are the minimum each range allows (^).
| Library | Version (package.json) |
Role |
|---|---|---|
| React | 19.2.4 (react, react-dom) |
UI framework |
| Redux Toolkit | 2.2.1 (@reduxjs/toolkit) |
State management |
| React Router | 6.22.3 (react-router-dom) |
Client-side routing |
| Tailwind CSS | 3.4.1 (tailwindcss) |
Utility-first CSS |
| Material UI | 5.15.12 (@mui/material, @mui/icons-material) |
Component library |
| Freshworks Crayons | 4.2.0 (@freshworks/crayons) |
Freshworks design system |
| date-fns | 3.3.1 | Date formatting |
Tests live in tests/ and use Jest 29.7.0 with jsdom (see devDependencies in package.json).
npm testTest files:
| File | Covers |
|---|---|
tests/App.test.js |
App component rendering |
tests/reducers/counterReducer.test.js |
Counter Redux reducer |
tests/reducers/ticketsReducer.test.js |
Tickets Redux reducer |
tests/hooks/useFreshworksData.test.js |
Freshworks data hook |
End-to-end checks for request templates, SMI, instance / interface APIs, and installation parameters require a running dev server and dev=true. See §4 Test in Freshdesk and §5 Freshservice (full page).
| Command | Description |
|---|---|
fdk run |
Start the local dev server |
fdk validate |
Validate app structure and lint |
fdk pack |
Package app for marketplace submission |
npm test |
Run the Jest test suite |
npm test -- --coverage |
Run tests with coverage report |
npm run sync:readme |
Refresh version badges and tables from manifest.json + package.json |
Contributions are welcome. Please follow these steps:
- Fork the repository.
- Create a branch for your feature or fix:
git checkout -b feat/my-feature
- Make your changes. Keep commits focused and descriptive.
- Validate before pushing:
fdk validate npm test - Push and open a Pull Request against
main.
- After bumping
manifest.jsonenginesorpackage.jsondependencies, runnpm run sync:readme. - Run
fdk validateand ensure 0 platform errors and 0 lint errors before submitting. - All new features should include tests.
- Use Conventional Commits for commit messages (
feat:,fix:,docs:,chore:). - Keep PRs small and focused on a single change.
- Do not commit
node_modules/,.fdk/,log/, orcoverage/.
Open an issue with:
- A clear title and description.
- Steps to reproduce.
- Expected vs actual behavior.
- FDK version (
fdk version) and Node.js version (node -v).
This project is proprietary and licensed for Freshworks-product usage only. See LICENSE.md for terms and restrictions.
| Topic | Link |
|---|---|
| Freshworks Developer Portal | developers.freshworks.com |
| Platform 3.0 SDK Docs | SDK Documentation |
| Freshdesk API Reference | developers.freshdesk.com/api |
| Freshworks Crayons | crayons.freshworks.com |
| Developer Community | community.freshworks.dev |
| FDK CLI Reference | CLI Documentation |
Built for the Freshworks Developer Community
