Smart Document Automation:: From Zoho Projects to Zoho Writer – Merge, Edit, and Share

Smart Document Automation:: From Zoho Projects to Zoho Writer – Merge, Edit, and Share

Hello Everyone, 

A custom function is a software code that can be used to automate a process and this allows you to automate a notification, call a webhook, or perform logic immediately after a workflow rule is triggered. This feature helps to automate complex tasks and calculations.

In this post, I will present a specific use case that demonstrates the integration between Zoho Projects and Zoho Writer.


Use Case:

Merge data from Zoho Projects into a predefined template in Zoho Writer.
Automatically generate a fillable document.
Share the document link with a designated user, either specified within Zoho Writer or Zoho Projects.
Include fillable fields within the merge template, allowing the recipient to complete the necessary information.

This functionality has been successfully implemented using Custom Functions. The following steps outline the setup process. Refer to the attached screenshots for additional context.

Step 1: Navigate to Zoho Writer → Automate → Merge Template
Step 2: Click on 'Convert'
Step 3: Select a data source to start merging
Step 4: Select 'Custom Source'
Step 5: Click on 'Use' next to the Projects Fields
Step 6: Once done, enter the Zoho Org ID i.e., ZOID within the portalID.
Step 7: Click on 'Connections' within the same page → Create a Connection with the mentioned scopes.
Step 8: Click on 'Manage Fields' → Drag and Drop the required fields.
Step 9: You can setup the Merge options as per your concern as in this screen shot
Step 10: Click on 'Merge & Share Fillable Link'
Step 11: Select the required field from the 'Fields & Buttons'
Step 12: Configure the 'Merge & share fillable link'
Step 13: Click on 'Configure from submit actions' → Webhook & Custom Functions → Execute Custom Functions.

The following functionality can be achieved using the code provided below. Additionally, two connections must be created as outlined

  1. Zoho Projects Connection :: Create a connection for the Zoho Projects service with the following scopes 'ZohoProjects.projects.READZohoProjects.milestones.READZohoProjects.tasklists.READZohoProjects.tasks.READZohoProjects.users.READ, and ZohoProjects.clients.READ'.

    Update the placeholder 'xxxxxxxxx' with the actual connection name in ZPConnection.

  2. Zoho OAuth Connection :: Create a connection for the Zoho OAuth service with the following scopes 'ZohoWriter.documentEditor.ALL and ZohoWriter.Merge.ALL'.

    Replace 'yyyyyyyyy' with the actual connection name in ZWConnection.

For detailed guidance on creating a connection, please refer to this link. The required code is provided below.

//connections for authentication
tomailaddress = "a@b.com";
ZPConnection = "xxxxxxxxx";
ZWConnection = "yyyyyyyyy";
projectsDomain = "https://projectsapi.zoho.com/";
writerDomain = "https://zohoapis.com/";
headers = Map();
// Mandatory header - Do not remove
headers.put("x-zoho-service","ZohoWriter");
//Domain url for Projects and Writer
projectsDetailsUrl = projectsDomain + "restapi/portal/" + portalId + "/projects/" + projectId + "/";
milestoneDetailsUrl = projectsDomain + "restapi/portal/" + portalId + "/projects/" + projectId + "/milestones/";
taskListDetailsUrl = projectsDomain + "restapi/portal/" + portalId + "/projects/" + projectId + "/tasklists/";
taskDetailsUrl = projectsDomain + "restapi/portal/" + portalId + "/projects/" + projectId + "/tasks/";
projectsUsersUrl = projectsDomain + "/api/v3/portal/" + portalId + "/projects/" + projectId + "/users";
clientUsersUrl = projectsDomain + "api/v3/portal/" + portalId + "/projects/" + projectId + "/clients/users";
mergeFieldsUrl = writerDomain + "/writer/api/v1/documents/" + templateId + "/fields";
mergeAndStoreUrl = writerDomain + "/writer/api/v1/documents/" + templateId + "/merge/sharetofill";
projectsCommentUrl = projectsDomain + "api/v3/portal/" + portalId + "/projects/" + projectId + "/comments";
getAllFields = invokeurl
[
url :mergeFieldsUrl
type :GET
connection:"writer"
];
if(!getAllFields.contains("error") && getAllFields.getJSON("merge").size() > 0)
{
values_map = Map();
projectDetails = invokeurl
[
url :projectsDetailsUrl
type :GET
headers:headers
connection:"connectionprojects"
];
if(!projectDetails.contains("error"))
{
projectFieldValues = projectDetails.getJson("projects").get(0);
projectCF = false;
if(projectFieldValues.contains("custom_fields"))
{
projectCF = true;
projectCustomfields = projectFieldValues.getJSON("custom_fields");
}
for each  mergeField in getAllFields.getJson("merge")
{
if(projectFieldValues.containsKey(mergeField.getJSON("id")))
{
if(mergeField.getJSON("id") == "TAGS")
{
tagList = List();
for each  tag in projectFieldValues.getJSON("TAGS")
{
tagList.add(tag.getJSON("name"));
}
values_map.put(mergeField.get("id"),tagList);
continue;
}
else
{
values_map.put(mergeField.get("id"),projectFieldValues.getJSON(mergeField.getJSON("id")));
continue;
}
}
// merge project customfields
if(projectCF == true)
{
for each  field in projectCustomfields
{
if(field.containsKey(mergeField.getJSON("id")))
{
values_map.put(mergeField.get("id"),field.getJSON(mergeField.getJSON("id")));
break;
}
}
}
// merge milestone details
if(mergeField.get("id") == "milestone_details")
{
milestoneDetilList = List();
MilestoneDetails = invokeurl
[
url :milestoneDetailsUrl
type :GET
headers:headers
connection:"connectionprojects"
];
if(!MilestoneDetails.contains("error"))
{
allMilestonedetails = MilestoneDetails.getJson("milestones");
for each  milestone in allMilestonedetails
{
milestoneDetilMap = Map();
for each  milestoneField in mergeField.getJSON("fields")
{
mergeFieldId = milestoneField.get("id");
mergeFieldName = mergeFieldId.subString("milestone_details.".length(),mergeFieldId.length());
//merge milestone fields
if(milestone.containsKey(mergeFieldName))
{
if(mergeFieldName == "tags")
{
tagList = List();
for each  tag in milestone.getJSON(mergeFieldName)
{
tagList.add(tag.getJSON("name"));
}
milestoneDetilMap.put(mergeFieldId,tagList);
continue;
}
else if(mergeFieldName == "status")
{
milestoneDetilMap.put(mergeFieldId,milestone.getJson("status_det").getJson("name"));
continue;
}
else
{
milestoneDetilMap.put(mergeFieldId,milestone.getJson(mergeFieldName));
continue;
}
}
}
milestoneDetilList.add(milestoneDetilMap);
}
values_map.put("milestone_details",milestoneDetilList);
}
}
// merge tasklist details
if(mergeField.get("id") == "tasklist_details")
{
taskListDetilList = List();
getTasklistDetails = invokeurl
[
url :taskListDetailsUrl
type :GET
headers:headers
connection:"connectionprojects"
];
if(!getTasklistDetails.contains("error"))
{
taskListdetails = getTasklistDetails.getJson("tasklists");
for each  tasklist in taskListdetails
{
taskListDetilMap = Map();
for each  taskListField in mergeField.getJSON("fields")
{
mergeFieldId = taskListField.get("id");
mergeFieldName = mergeFieldId.subString("tasklist_details.".length(),mergeFieldId.length());
//merge tasklist fields
if(tasklist.containsKey(mergeFieldName))
{
if(mergeFieldName == "tags")
{
tagDetails = List();
for each  tag in tasklist.getJson(mergeFieldName)
{
tagDetails.add(tag.getJSON("name"));
}
taskListDetilMap.put(mergeFieldId,tagDetails);
continue;
}
else
{
taskListDetilMap.put(mergeFieldId,tasklist.getJson(mergeFieldName));
continue;
}
}
}
taskListDetilList.add(taskListDetilMap);
}
values_map.put("tasklist_details",taskListDetilList);
}
}
// merge task details
if(mergeField.get("id") == "task_details")
{
taskDetilList = List();
getTaskDetails = invokeurl
[
url :taskDetailsUrl
type :GET
headers:headers
connection:"connectionprojects"
];
if(!getTaskDetails.contains("error"))
{
taskdetails = getTaskDetails.getJson("tasks");
for each  taskdetail in taskdetails
{
taskDetilMap = Map();
taskCFs = false;
if(taskdetail.contains("custom_fields"))
{
taskCFs = true;
taskCustomfields = taskdetail.getJSON("custom_fields");
}
for each  taskField in mergeField.getJSON("fields")
{
mergeFieldId = taskField.get("id");
mergeFieldName = mergeFieldId.subString("task_details.".length(),mergeFieldId.length());
//merge task fields
if(taskdetail.containsKey(mergeFieldName))
{
if(mergeFieldName == "tasklist" || mergeFieldName == "status")
{
taskDetilMap.put(mergeFieldId,taskdetail.getJson(mergeFieldName).getJson("name"));
continue;
}
else if(mergeFieldName == "tags")
{
tagDetails = List();
for each  tag in taskdetail.getJson(mergeFieldName)
{
tagDetails.add(tag.getJSON("name"));
}
taskDetilMap.put(mergeFieldId,tagDetails);
continue;
}
else
{
taskDetilMap.put(mergeFieldId,taskdetail.getJson(mergeFieldName));
continue;
}
}
else if(taskdetail.containsKey("details"))
{
if(mergeFieldName == "owners")
{
ownerDetail = List();
ownerList = taskdetail.getJson("details").getJSON("owners");
for each  owner in ownerList
{
ownerDetail.add(owner.getJson("name"));
}
taskDetilMap.put(mergeFieldId,ownerDetail);
continue;
}
}
else if(taskdetail.containsKey("log_hours"))
{
if(mergeFieldName == "billable_hours" || mergeFieldName == "non_billable_hours")
{
taskDetilMap.put(mergeFieldId,taskdetail.getJson("log_hours").getJSON(mergeFieldName));
continue;
}
}
//merge task customfields
if(taskCFs == true)
{
for each  taskCF in taskCustomfields
{
if(taskCF.containsKey(mergeFieldName))
{
taskDetilMap.put(mergeFieldId,taskCF.getJson(mergeFieldName));
break;
}
}
}
}
taskDetilList.add(taskDetilMap);
}
values_map.put("task_details",taskDetilList);
}
}
// merge projectUsers
if(mergeField.get("id") == "project_users")
{
projectUserList = List();
getProjectUsers = invokeurl
[
url :projectsUsersUrl
type :GET
connection:"connectionprojects"
];
if(!getProjectUsers.contains("error"))
{
projectUserdetails = getProjectUsers.getJson("users");
for each  userDetails in projectUserdetails
{
projectUsersMap = Map();
for each  userField in mergeField.getJSON("fields")
{
mergeFieldId = userField.get("id");
mergeFieldName = mergeFieldId.subString("project_users.".length(),mergeFieldId.length());
//merge projectusers fields
if(mergeFieldName == "role")
{
if(userDetails.get(mergeFieldName).size() > 0)
{
projectUsersMap.put(mergeFieldId,userDetails.getJSON(mergeFieldName).getJSON("name"));
continue;
}
}
else if(mergeFieldName == "profile")
{
if(userDetails.get(mergeFieldName).size() > 0)
{
projectUsersMap.put(mergeFieldId,userDetails.getJSON(mergeFieldName).getJSON("name"));
continue;
}
}
else
{
projectUsersMap.put(mergeFieldId,userDetails.getJSON(mergeFieldName));
continue;
}
}
projectUserList.add(projectUsersMap);
}
values_map.put("project_users",projectUserList);
}
}
// merge clients users
if(mergeField.get("id") == "client_users")
{
clientUserList = List();
getProjectClientUser = invokeurl
[
url :clientUsersUrl
type :GET
connection:"connectionprojects"
];
if(!getProjectClientUser.contains("error"))
{
clientUserdetails = getProjectClientUser.getJson("clients").getJSON("users");
for each  userDetails in clientUserdetails
{
clientUsersMap = Map();
for each  userField in mergeField.getJSON("fields")
{
mergeFieldId = userField.get("id");
mergeFieldName = mergeFieldId.subString("client_users.".length(),mergeFieldId.length());
// merge clientsUsers
if(!mergeFieldName == "profile")
{
clientUsersMap.put(mergeFieldId,userDetails.getJSON(mergeFieldName));
continue;
}
else
{
clientUsersMap.put(mergeFieldId,userDetails.getJSON("profile").getJson("name"));
continue;
}
}
clientUserList.add(clientUsersMap);
}
values_map.put("client_users",clientUserList);
}
}
}
mergeDataParams = Map();
mergeDataParams.put("merge_data",{"data":values_map});
mergeAndStore = invokeurl
[
url :mergeAndStoreUrl
type :POST
parameters:mergeDataParams
connection:"writer"
];
if(!mergeAndStore.containsKey("error") && mergeAndStore.containsKey("merge_report_url"))
{
   mailMessage =  "Fill in the document " + mergeAndStore.get("records").get(0).get("fillable_link");
sendmail
[
from :zoho.loginuserid
to :tomailaddress
subject :"Fillable document link"
message :mailMessage
]
}
return mergeAndStore;
}
else
{
return projectDetails;
}
}
else
{
return getAllFields;
}

Creating custom functions in Zoho Projects is a straightforward process, supported by comprehensive documentation. Zoho offers a variety of built-in functions that serve as useful starting points, and you can also define your own functions using Zoho’s scripting language, Deluge. Implementing these functions can significantly enhance efficiency and improve productivity.

Stay tuned for additional examples and custom function scripts.

    • Sticky Posts

    • Time Log Reminder

      Tracking the time spent on tasks and issues is one of the most important functions of a timesheet. However, users may forget to update the time logs because they have their own goals to achieve. But, time logs must be updated at regular intervals to keep
    • Introducing the Zoho Projects Learning Space

      Every product has its learning curve, and sometimes having a guided path makes the learning experience smoother. With that goal, we introduce a dedicated learning space for Zoho Projects, a platform where you can explore lessons, learn at your own pace,
    • Update on V2 API End-of-Life Timeline

      Dear Users, Earlier this year, we shared the launch of the V3 APIs and requested users to migrate from the older V2 APIs by December 2025. We have received valuable feedback from our users and partners regarding their migration timelines. We are happy
    • Automation Series: Auto-update Phase Status

      Hello Folks! You can auto-update your phase's status based on status of underlying tasks using custom functions. In this series, we will showcase how to create and run custom functions, using Deluge, with ease. Follow the steps below and automate your
    • Automate Timesheet Approvals with Multi-level Approval Rules

      Introducing Approval Rules for Timesheets in Zoho Projects. With this automation, teams can manage how timesheets are reviewed and approved by setting up rules with criteria and assigning approvers to handle submissions. Timesheet, when associated to
    • Recent Topics

    • Read webpage - MSXML2.ServerXMLHTTP

      I have the following VBA script, put together from various sources (mainly zoho forum/help/support, so it once worked, I guess): private Sub GetListOfSheets() Dim url As String Dim xmlhttp As Object Dim parameters As String Dim html As String range("B1").value
    • Zoho Creator Form Search

      I'm new to Zoho Creator and so far i do like it but im shocked there seems to be no easy way to add a search function and/or navigation to there forms as this readily available in every platform i have used in the past. Anyway i have a form which i have
    • How to setup DKIM / SPF in Zoho Mail

      Hello there! I'm having a hard time understanding how to setup the TXT records for SPF and DKIM with Zoho Mail. I'm following the instructions here: https://www.zoho.com/campaigns/help/setup-spf-dkim.html Specifically, when it comes to the value for the
    • Mass update cant trigger a workflow?

      Hello there, Is this a normal Creator behaviour? In a report, if I manually edit each data row, it will pass those changes to all other reports. However, if I perform a Mass Update on a few data rows at once, the new data is not pushed to other reports.
    • search and Smart Bar both missing in Mail

      One of the users on my account does not have the search bar at the top right or the Smart Bar at the bottom left of the desktop Mail app. Any ideas how to get those back?
    • Let us add Lookup fields in the Blueprint Transitions

      We are unable to add Lookup Fields in the blueprint transitions in Zoho Desk, we wanted to make it a requirement for our workflow but since it's not available in the transition we cannot. The lookup field exists in the Layout: But it cannot be added/selected
    • Zoho Projects API Scope for Issue Attachments

      I try to download issue attachments via the API, unfortunately I always get the following error code when trying to download the file from the supplied URL: {"ERROR_MESSAGE":"INVALID_OAUTHSCOPE","ERROR_CODE":401} indicating an auth scope error. I've tried
    • How to fetch custom fields for time entries in Zoho Project API v3

      In the previous Zoho Projects REST API, we were able to retrieve custom field details for time entries, including picklist options, using the endpoint: GET /restapi/portal/[PORTAL_ID]/timesheetcustomfields In the new Zoho Projects API v3, we tried using
    • Zoho Survey Enhancements

      We love Survey. We use it a ton. It needs some enhancements. Maybe some of these are already on the roadmap? API - this is crucial. We have some complex surveys that take place and need to update records, trigger other functions/automations, etc. I would
    • Images Don't Display on Blog

      Hi, I've just posted a new blog entry, but regardless of format (png or jpeg) Images that display in Preview mode do not display on any browser in the published post.  I've not done anything different with this post than others regarding images, please advice. Thanks! Edit:  I've discovered that moving the image files from a my sub directory folder "Pics for Blog" to the top level of the "Files" folder on my site allows the images to display live.  This, even though my other blog posts are correctly
    • Checkbox Field Mapping Zoho Forms to Zoho Sign

      I have an application that will be filled out via Forms that I am mapping into a PDF Filler on Zoho Sign. I need to have a series of checkboxes mapped to the corresponding fields in Sign and I only see single checkboxes available via the mapping, though
    • Books P&L by Customer

      Lately, we have found that the Customer field filter for the Profit & Loss report in Zoho Books is no longer available. We have several projects with the same customer and look at the P&Ls by project AND by Customer (overall). Can you please add back in this field selection alongside the Project field to the P&L report filter? Thanks! -Gina
    • Sent mail sort by date disappeared

      Hello, We used to be able to sort the emails by date in the sent folder, but this feature has recently disappeared. Can we bring it back?
    • I cannot check out to Zoho People.

      When I tried to check out today, there's prompt that inhibits me to check out: To add entry in Attendance, log time for any of your jobs
    • Pricing Strategies: #2 Plan your Service Offerings using Plans

      Meet Harvey & Mia, hailing from the same Tech school, carrying the dream of setting up their own IT maintenance firm in their location. They drew up a clear business plan and put their business in motion on New Year's Eve. They had great reception right
    • Open a record to edit based on another form being edited

      Hi all, I am trying to edit a record based on another record being edited. It works out like this: When I create a Monthly_Update and then submit I open the Monthly_Levels form and pass the ID from the Monthly_Update to a field called mu. I want it to
    • Problem with signature on zoho survey

      Hello, I'm trying to export individual responses with signatures on zoho survey. But the signatures on some of my surveys are not exported as the original image, but as a generic image, same for all (screen joins). Is there a solution to have the signatures
    • Change in Zoho CRM API?

      Hello, I am using the Zoho API trough the PHP SDK v2.1 Since few days, I noticed that I have to change the way I pass the data to the API when I create, update, or upsert a record. Dates Before I was passing a PHP date object to "$record->addKeyValue(...)",
    • MTA - BAD IP reputation by outlook/hotmail

      Messages to Microsoft email servers are bouncing back due to poor reputation. Message: 4.7.650 The mail server [136.143.188.206] has been temporarily rate limited due to IP reputation. For e-mail delivery information see https://postmaster.live.com (S775)
    • QuickBooks Extension for Zoho CRM - Advanced Features -2025

      Hello Everyone, We’re happy to announce the latest version of our QuickBooks Extension for Zoho CRM, now officially live on the Zoho Marketplace! This release introduces one-click data sync, a user-friendly UI, enhanced performance, and a powerful set
    • Changing Department often causes the Firefox tab to freeze

      Title, it doesn't seem to happen with neither Opera nor Chrome. And even in Firefox, sometimes it just lets me change the department I'm in no problem, even to All Departments which is probably the most, like, resource heavy? But most of the time, the
    • Need to integrate Zoho Mail Mobile app with Zoho Meeting Mobile App for Android and Apple

      Hello Zoho Team, Please bring integration of Zoho Mail Mobile app with Zoho Meeting for Android and Apple Thanks
    • ¡Muchas gracias por participar a los Meetups de Usuarios de Zoho! Y Novedades del ecosistema Zoho

      ¡Hola Comunidad de Zoho en Español! 👋 Después de un breve lapso de tiempo, volvemos con una nueva edición de nuestro Community Digest, donde te contamos las novedades de los productos de Zoho en los últimos meses. Estas mejoras se centran en nuestros
    • Anyway to move mail from one account to another yet?

      Hello, Is there any way to move email from one mailbox account to another mailbox account in zoho yet? Thanks, Ryan.
    • Using a CRM Client Script Button to create a Books Invoice

      Hello, I need help handling error messages returned to my client script from a function. The scenario I have setup a client script button which is available from each Deal. This CS executes a crm function, which in turn creates an invoice based on the
    • Building Toppings #2 - Learn how to use Bigin's Developer Console to build toppings

      Hey Biginners, In our last post, we discussed what toppings are, why they're essential to extending Bigin's capabilities, and how the Bigin Developer Center serves as the starting point for building them. As a cloud platform, the Developer Center empowers
    • Links are incorrect when sent out

      I'm adding in hyperlinks into my eDM. When I send a test email, it's all correct. However, when I send out the eDM, all the hyperlinks jump up one space so none of the links are opening to the correct page. Why is this happening and how can I fix it?
    • Tip of the week #16 - Search and filter threads based on criteria

      Zoho TeamInbox lets you search and filter threads with any information that you have about the thread. You just have to input the criteria and Zoho TeamInbox will list all the threads that match the condition.   Firstly, there is a global search you can
    • Introducing recipient authentication via Stripe Identity in Zoho Sign

      Hi everyone! It's important to authenticate your recipient's identity before they access and sign important documents to ensure the highest level of compliance. Zoho Sign already helps businesses do this with various authentication methods: SMS OTP Email
    • Keep Converted Leads

      How do I keep the converted leads in the Leads Module after conversion (converting it to account, contact, deal). I want to add it in a converted stage in the leads module in order to get a report or dashboard and see all converted leads from my pip
    • Customizing Global Search Settings for All Users

      Hi Our team use the brilliant global search functionality within CRM many many times daily. But, we struggle with the out-of-the box columns that CRM gives you. We are always telling users to customize this look to more suit our business, to show the
    • Introducing Formula Fields for performing dynamic calculations

      Greetings, With the Formula Field, you can generate numerical calculations using provided functions and available fields, enabling you to derive dynamic data. You can utilize mathematical formulas to populate results based on the provided inputs. This
    • Tip of the Week #77– Stay informed of the activities happening in your organization

      Whenever a message is handled in Zoho TeamInbox, every action is recorded in the Activity Log. This ensures you always know what’s happening across your teams and inboxes. To access it, simply click the Audits icon on the left pane’s top bar after logging
    • Zoho Logs - Not seeing logs since 30 Nov

      Hi, we have a few functions running, I am testing some new ones and noticed that although I can see executions, I cannot see any logs, even when the first line on the functions is a log. I reviewed some existing functions, one of which is invoked on a
    • Workdrive MS Office integration

      Have installed subscribed version of Zoho WorkDrive VSTO runtime not found is the error when I try to install Zoho_WorkDrive_For_Office Unable to open work files in Excel and Word Urgent, since I have migrated all my OneDrive files to work drive already
    • How to change Zoho Vault password

      I am searching where I can change the Vault Password after having changed the Zoho account password. I don't see this option anywhere in my account. It should be simple and accessible! Please help. Thanks!
    • Zoho Workdrive - Communication / Chat Bar

      Hi Team, Please consider adding an option to allow admins to turn on or off the Zoho Communication Bar. Example of what I mean by Communication Bar: It's such a pain sometimes when I'm in WorkDrive and I want to share a link to a file with a colleague
    • When Marking a Multiple Choice Answer Exclusive - Not Following My Survey Disqualification Logic

      Using a multiple choice (many answers) question and I created survey disqualification logic that was working as intended. My question: Disqualification page logic is: If (QUESTION) is "any one of the following" then (OPTIONS) - a custom message populates
    • Where to Add Machines as Products to Map with Assets in Zoho FSM?

      implementing Zoho FSM for a clinical equipment supply company. The business sells and installs clinical machines in hospitals and clinics, and they also handle service requests, scheduled maintenance, calibration visits, and general machine upkeep. In
    • Show Zoom Link in Recipient's Calendar

      We set up meetings within a record, selecting the "Make this an online meeting".  We use Zoom. Most of the recipients go to their calendar (usually Gmail or Outlook, corporate) to join the Zoom meeting, but there is no Zoom link in the calendar. Can this
    • Next Page