Welcome back to another week of Kaizen!
In our
sample project, we used the
file-based persistence method, a simple setup where the access and refresh tokens are stored in a local file. While this method is great for getting started, it might not always fit your business requirements.
That is why our SDKs offer multiple ways to persist your tokens.
This week, we will explore why token persistence matters for your app’s secure operation, and how to implement custom token persistence methods, including a practical example using SQLite.
Why does token persistence matter?
When a user logs in via OAuth, Zoho returns two tokens:
- An access token (valid for one hour), used to access Zoho CRM data.
- A refresh token, used to get a new access token when the current one expires.
If your app does not store these tokens properly, your users will be forced to log in again every time they make an API call. Or every time their access token gets expired. That is not inconvenient; it is a poor user experience.
When you use Zoho CRM SDKs, this is all handled for you behind the scenes. When you first authenticate with Zoho, the SDK stores your access and refresh tokens. Later, when a token expires, the SDK automatically uses the refresh token to get a new one. All you have to do is configure and initialize the SDK, and you are ready to start making API calls using the different methods offered by our SDKs!
From the user’s perspective, it means:
- They do not have to log in every time.
- Their sessions are automatically renewed without interruption.
- Token revocation can be done centrally.
From a developer’s perspective:
- You can control how and where tokens are stored.
- You have control to enforce policies like session timeouts or token cleanup.
Supported token persistence options
The Zoho CRM SDKs support three token persistence mechanisms:
File Persistence:
As we have already seen in last week's Kaizen, in this method, the tokens are stored in a local file of your choice. This can be configured while configuring and initializing the SDK. While this is simple and great for internal and local use, it might not always meet the needs of a growing business. For instance, if the file gets deleted or corrupted, you lose the tokens. It also poses a security risk, as storing tokens in files may expose them to unauthorised access if the file is not properly secured.
Database Persistence:
This stores tokens in a MySQL database, making it better suited for production environments. It is more robust and can handle larger-scale user management.
Using this persistence method, you can only provide the following connection parameters - host, DB name, table name, username, password, and port number.
Custom Persistence:
But what if neither of these options fits your needs? Maybe you are working in an environment without traditional storage like
AWS's Secret Manager, or you prefer any other Database, or running a microservice in a container where local storage is more practical. That is where Custom Token Persistence comes in.
Custom Token Persistence
Custom persistence means you can implement your own logic for storing and retrieving OAuth tokens, instead of relying on the SDK’s default mechanism. To do this, you should create a class that implements the TokenStore interface and override a standard set of methods, each handling a specific part of the token lifecycle.
Here’s what your custom class must implement:
Method
| Purpose | Return Type |
find_token(self, token) | Given a token, return a full Token (OAuthToken) object from storage. Used before making any CRM API call. | Token(OAuthToken) object |
save_token(self, token) | Called right after Zoho returns a new access/refresh token. Your implementation must persist it. | None |
delete_token(self, id) | Delete a specific token using its unique ID. | None |
get_tokens(self) | Return all stored tokens. | A list of Token(OAuthToken) objects |
delete_tokens() | Delete all stored tokens. Useful during cleanup or logout. | None |
find_token_by_id(id) | Retrieve a token by its unique identifier. | Token(OAuthToken) object |
The token object is an instance of
OAuthToken. The SDK will invoke these methods automatically as part of its flow. You just have to focus on where and how to store the tokens. With this, you can persist tokens to any storage as long as your class handles these methods correctly.
Understanding the token object
Before we dive deeper into custom token persistence, let's clarify what this token (OAuthToken) object is and how you should work with it.
The token object is an instance of
OAuthToken. This class bundles all the credentials and details the SDK needs to authenticate your API requests. Here’s what it holds:
- access_token
- refresh_token
- client_id
- client_secret
- redirect_url
- expires_in
- user_signature
- id
- api_domain
Implementing Custom Token Persistence with SQLite
Now that we've covered the basics of token persistence and how Zoho SDK supports custom stores, let’s dive into a practical, real-world example using SQLite as the backend for storing tokens.
SQLite is a lightweight, file-based database engine. It is perfect when you want a persistent store without the complexity of a full database server.
The CustomStoreSQLite Class
This class implements all six required methods of the TokenStore interface using SQLite as the backend.
1. Initialization and Table Setup
When you create a CustomStoreSQLite object, it immediately checks if the token table exists in the SQLite database file zohooauth.db. If the DB or the table is missing, its __init__() method creates one with all the necessary columns to store token details like id, user_name, client_id, client_secret, refresh_token, access_token, grant_token, expiry_time, redirect_url and api_domain.
def __init__(self):
""" Initializes the SQLite database and sets up the oauthtoken table if needed. """ self.db_name = 'zohooauth.db' if not self.check_table_exists(): connection = sqlite3.connect(self.db_name) cursor = connection.cursor() cursor.execute("CREATE TABLE oauthtoken (id varchar(10) NOT NULL,user_name varchar(255), client_id " "varchar(255), client_secret varchar(255), refresh_token varchar(255), access_token " "varchar(255), grant_token varchar(255), expiry_time varchar(20), redirect_url varchar(" "255), api_domain varchar(255), primary key (id))") cursor.close() |
This means the first time your app runs, it sets up its own database schema automatically.
2. Saving a Token - save_token(self, token)
Purpose:
This method is called every time Zoho returns a new token, whether after a login or a token refresh. Your implementation is responsible for safely persisting this token, typically by upserting (inserting or updating) a row in your database that uniquely identifies the token’s user and client combination.
Expected behaviour:
The method must store the token in your custom database or storage system.
- If a matching token already exists (based on user, refresh token, or client credentials), it should be updated.
- If no match exists, a new entry must be created.
- Tokens should not be duplicated. Multiple users should be managed separately.
Input Parameters: An instance of Token(OAuthToken) class containing details like access token, refresh token, user signature, client ID/secret, etc.
Return value: None. But must raise exceptions on failure.
Sample Implementation using SQLite:
Here is the logic used in the implementation of save_token() method:
- If the user name is available, use it to update the token.
- If no user name but the access token is available in the table, update by the access token.
- If there is a refresh or grant token with the same client credentials, then update accordingly.
- If none of these match, insert as a new row.
def save_token(self, token):
if not isinstance(token, OAuthToken): return cursor = None connection = None try: connection = sqlite3.connect(self.db_name) oauth_token = token query = "update oauthtoken set " if oauth_token.get_user_signature() is not None: name = oauth_token.get_user_signature().get_name() if name is not None and len(name) > 0: query = query + self.set_token(oauth_token) + " where user_name='" + name + "'" elif oauth_token.get_access_token() is not None and len(oauth_token.get_access_token()) > 0 and \ self.are_all_objects_null([oauth_token.get_client_id(), oauth_token.get_client_secret()]): query = query + self.set_token( oauth_token) + " where access_token='" + oauth_token.get_access_token() + "'" elif ((oauth_token.get_refresh_token() is not None and len(oauth_token.get_refresh_token()) > 0) or (oauth_token.get_grant_token() is not None and len( oauth_token.get_grant_token()) > 0)) and oauth_token.get_client_id() is not None \ and oauth_token.get_client_secret() is not None: if oauth_token.get_grant_token() is not None and len(oauth_token.get_grant_token()) > 0: query = query + self.set_token( oauth_token) + " where grant_token='" + oauth_token.get_grant_token() + "'" elif oauth_token.get_refresh_token() is not None and len(oauth_token.get_refresh_token()) > 0: query = query + self.set_token( oauth_token) + " where refresh_token='" + oauth_token.get_refresh_token() + "'" query = query + " limit 1" try: cursor = connection.cursor() cursor.execute(query) if cursor.rowcount <= 0: if oauth_token.get_id() is not None or oauth_token.get_user_signature() is not None: if oauth_token.get_refresh_token() is None and oauth_token.get_grant_token() is None \ and oauth_token.get_access_token() is None: raise SDKException(Constants.TOKEN_STORE, Constants.GET_TOKEN_DB_ERROR1) if oauth_token.get_id() is None: newId = str(self.generate_id()) oauth_token.set_id(newId) query = "insert into oauthtoken (id,user_name,client_id,client_secret,refresh_token,access_token," \ "grant_token,expiry_time,redirect_url,api_domain) values (?,?,?,?,?,?,?,?,?,?);" val = (token.get_id(), token.get_user_signature().get_name() if token.get_user_signature() is not None else None, token.get_client_id(), token.get_client_secret(), token.get_refresh_token(), token.get_access_token(), token.get_grant_token(), token.get_expires_in(), token.get_redirect_url(), token.get_api_domain()) cursor.execute(query, val) except Error as e: raise e finally: connection.commit() cursor.close() if cursor is not None else None connection.close() if connection is not None else None except Exception as ex: raise SDKException(Constants.TOKEN_STORE, Constants.SAVE_TOKEN_DB_ERROR, cause=ex) |
3: Fetching a Token - find_token(self, token)
Purpose:
This is the method the SDK calls whenever it needs to make an API call on behalf of a user, but has only partial token information.
Depending on the token flow - Grant Token, Refresh Token, Access Token, or ID-based - only a specific token or ID may be provided during the API call. In such cases, find_token(self, token) method locates and return the complete OAuthToken object from storage if a matching one exists. If no matching token exists in the storage, this method will return None, and the SDK will proceed to generate a new token with the provided details and save it using the save_token(self, token) method.
Expected behavior:
- Based on the available details in the input token (user name, access token, refresh or grant token), this method should query storage and return a complete token object.
- If no match is found, it should return None.
Input Parameters: A partially filled Token(OAuthToken) object.
Return value: A fully populated Token object if found, or None.
Sample Implementation using SQLite:
The find_token(self, token) method implementation does the following:
- Dynamically builds a WHERE clause based on available attributes.
- Queries the database for a matching record.
- Fetches the matching record, if any, and populates the Token object with the full set of stored values (access token, refresh token, expiry time, etc.).
- Returns the Token object if a matching record is found, or return None.
Without this method, your app wouldn’t know which token to use during API calls. For example, consider the case when a user reopens your app after hours. You have their refresh token stored. The SDK calls find_token(self, token) to get the full token and proceeds without requiring a fresh login.
def find_token(self, token):
cursor = None connection = None try: connection = sqlite3.connect(self.db_name) if isinstance(token, OAuthToken): oauth_token = token query = "select * from oauthtoken" if oauth_token.get_user_signature() is not None: name = oauth_token.get_user_signature().get_name() if name is not None and len(name) > 0: query = query + " where user_name='" + name + "'" elif oauth_token.get_access_token() is not None and self.are_all_objects_null( [oauth_token.get_client_id(), oauth_token.get_client_secret()]): query = query + " where access_token='" + oauth_token.get_access_token() + "'" elif oauth_token.get_refresh_token() is not None or oauth_token.get_grant_token() is not None and \ oauth_token.get_client_id() is not None and oauth_token.get_client_secret() is not None: if oauth_token.get_grant_token() is not None and len(oauth_token.get_grant_token()) > 0: query = query + " where grant_token='" + oauth_token.get_grant_token() + "'" elif oauth_token.get_refresh_token() is not None and len(oauth_token.get_refresh_token()) > 0: query = query + " where refresh_token='" + oauth_token.get_refresh_token() + "'" query = query + " limit 1" cursor = connection.cursor() cursor.execute(query) result = cursor.fetchone() if result is None: return None self.set_merge_data(oauth_token, result) except Exception as ex: raise SDKException(Constants.TOKEN_STORE, Constants.GET_TOKEN_DB_ERROR1, cause=ex) finally: cursor.close() if cursor is not None else None connection.close() if connection is not None else None return token |
4: Deleting a Token - delete_token(self, id)
Purpose:
Delete a specific token record from storage based on a unique token ID. It is commonly used when a user logs out or an admin revokes access for a user.
Expected behaviour:
- Locate the token record by its unique ID.
- Delete the corresponding record from storage.
Input Parameters: The token ID to be deleted.
Return values: None
Sample Implementation using SQLite:
def delete_token(self, id):
cursor = None try: connection = sqlite3.connect(self.db_name) try: cursor = connection.cursor() query = "delete from oauthtoken where id= " + id + ";" cursor.execute(query) connection.commit() except Error as ex: raise ex finally: cursor.close() if cursor is not None else None connection.close() if connection is not None else None except Error as ex: raise SDKException(code=Constants.TOKEN_STORE, message=Constants.DELETE_TOKEN_DB_ERROR, cause=ex) |
5: Deleting All Tokens - delete_tokens(self)
Purpose: Delete all tokens from storage, typically used for global logout or cleanup scenarios.
Expected behaviour: Remove all token records from storage in a single operation.
Input Parameters: None
Return Values: None
Sample Implementation using SQLite:
def delete_tokens(self):
cursor = None try: connection = sqlite3.connect(self.db_name) try: cursor = connection.cursor() query = "delete from oauthtoken;" cursor.execute(query) self.connection.commit() except Error as ex: raise ex finally: cursor.close() if cursor is not None else None connection.close() if connection is not None else None except Error as ex: raise SDKException(code=Constants.TOKEN_STORE, message=Constants.DELETE_TOKENS_DB_ERROR, cause=ex) |
6: Fetch all tokens - get_tokens(self)
Purpose: Retrieve all currently stored tokens.
Expected behaviour:
- Query storage for all token records.
- Construct and return a list of token objects
Input Parameters: None
Return Value: A list of Token objects representing all stored tokens.
Sample Implementation using SQLite:
def get_tokens(self):
cursor = None try: connection = sqlite3.connect(self.db_name) tokens = [] try: cursor = connection.cursor() query = "select * from oauthtoken;" cursor.execute(query) results = cursor.fetchall() for result in results: oauth_token = object.__new__(OAuthToken) self.set_oauth_token(oauth_token) self.set_merge_data(oauth_token, result) tokens.append(oauth_token) return tokens except Error as ex: raise ex finally: cursor.close() if cursor is not None else None connection.close() if connection is not None else None except Error as ex: raise SDKException(code=Constants.TOKEN_STORE, message=Constants.GET_TOKENS_DB_ERROR, cause=ex) |
7. Finding a Token by ID - find_token_by_id(self, id)
Purpose: Retrieve a specific token by its unique id.
Expected behaviour:
- Search storage for a token with the given ID.
- If found, return the complete token object; if not, return None.
Input Parameters: The unique identifier of the token (id)
Return Values: Returns a fully populated Token(OAuthToken) object if found; otherwise, returns None.
Sample Implementation using SQLite:
This method should follows a similar pattern to find_token, but use the unique id as the search key.
def find_token_by_id(self, id):
cursor = None try: connection = sqlite3.connect(self.db_name) try: query = "select * from oauthtoken where id='" + id + "'" oauth_token = object.__new__(OAuthToken) self.set_oauth_token(oauth_token) cursor = connection.cursor() cursor.execute(query) results = cursor.fetchall() if results is None or len(results) <= 0: raise SDKException(Constants.TOKEN_STORE, Constants.GET_TOKEN_BY_ID_DB_ERROR) for result in results: self.set_merge_data(oauth_token, result) return oauth_token except Error as ex: raise ex finally: cursor.close() if cursor is not None else None connection.close() if connection is not None else None except Error as ex: raise SDKException(code=Constants.TOKEN_STORE, message=Constants.GET_TOKEN_BY_ID_DB_ERROR, cause=ex)
|
Please find the complete custom_store_sqlite.py file
here.
How to use this in your project
To start using this custom token persistence class in your own Python project, follow these steps:
- Download the custom_store_sqlite.py and place this inside your project directory.
- Import the class in the script where you initialize the SDK. In our sample project, this is the record.py file.
from store.custom_store_sqlite import CustomStoreSQLite - In the SDK configuration, use the CustomStoreSQLite method instead of the FireStore method:
def init(self, client_id, code, location, redirect_url):
environment = DataCenter.get(location) client_secret = "17565609051856218813123b9a98de52c301722b7d" logger = Logger.get_instance(level=Logger.Levels.INFO, file_path="./logs.txt") store = CustomStoreSQLite() token = OAuthToken(client_id=client_id, client_secret=client_secret, grant_token=code, redirect_url=redirect_url) Initializer.initialize(environment=environment, token=token, logger=logger, store=store) |
That’s it! With this, all token operations (save, fetch, delete) will be routed through your custom store backed by SQLite.
The above video demonstrates this is in action. You can see what the database looks like when populated.
More Custom Persistence Implementations
The advantage of using Zoho CRM SDKs is that it doesn't box you in. You are free to implement token persistence in a way that fits your business logic, team expertise, or project requirements. Whether you prefer SQLite, NoSQL, or something entirely different, the SDK gives you full control through the TokenStore interface.
In the SQLite example above, we walked through how to implement a custom store using a persistent file-based database. You need to implement all the methods as explained in the previous section, no matter where you decide to persist your tokens.
To make things easier, we have included two additional reference implementations:
- An in-memory store, where tokens are stored in a dictionary
- A list-based store, which keeps token records as simple lists
Each one fully implements the required methods of the TokenStore interface.
SQLite In-Memory DB
This implementation uses SQLite's in-memory mode (using ":memory:") to store tokens in RAM. Here, we have implemented all the required methods from the TokenStore interface: find_token(), save_token(), delete_token(), get_tokens(), delete_tokens() and find_token_by_id().
Please find the
custom_store_in_memory.py file
here.
List-Based Persistence Using Simple Lists
The second reference implementation is a list-based token store that keeps token records in an in-memory Python list of lists. Each inner list represents a token’s attributes, such as ID, user signature, client ID, access token, refresh token, and so on.
This custom store fully implements all required methods from the TokenStore interface.
Please find the
custom_store_list.py file
here.
We hope this was useful and gives you enough info to build your own token persistence methods tailored to your needs. We used Python SDK here, but you can apply the same logic with any of our other SDKs. It is all the same logic, just different programming languages. Just remember to implement the required methods exactly as expected by the SDK, as explained here.
Give it a try, and please let us know how it goes or if you hit any bumps! Comment below, or send an email to
support@zohocrm.com. We will be waiting to hear from you!
Happy coding!
We are excited to be approaching the 200th post in our Kaizen series! As we get closer to this milestone, we would love to hear from you. Have questions, suggestions, or topics you would like us to cover in our future Kaizen posts? Your feedback helps us make the series even better.
Please take a moment to share your thoughts with us using this form - we'd really appreciate it!
Recent Topics
Feature Request: Dynamic Dimension Control for zc_LoadIn Popups
As detailed in this community discussion, Zoho Creator's zc_LoadIn parameter is a vital tool for opening components (forms, reports, or pages) in modal dialogs via HTML snippets, Notes, or Rich Text Fields. While powerful, this feature suffers from a
Subforms in stateless forms
I think the title says it all. We need to be able to add subforms to stateless forms. Currently the only workaround is to create a Form and delete each record upon submission of the form. I need to build an interface to update our inventory. Basically
Text wrap column headers in reports?
Is it possible to auto wrap column headers so that a longer multi-word header displays as two lines when the column is narrower than the width of the header title?
What’s New in Zoho Inventory — Latest Features, Integrations & Updates | December 2025
Zoho Inventory has evolved significantly over the past months, bringing you smarter, faster, and more connected tools to streamline your operations. Whether you’re managing multichannel sales, complex fulfillment workflows, or fast-moving stock, our newest
Marketer’s Space - Multi-Channel Campaigns for the Biggest Shopping Week with Zoho Marketing Automation
Hello marketers, Welcome back to another post in Marketers Space! The biggest shopping week of the year is almost here, and it’s your moment to shine without the stress. With Black Friday and Cyber Monday just around the corner, being present across email,
Pricing Strategies: #5 Stay local, Price & Sell Global
Arun had always dreamed of taking his handmade craft business beyond his hometown. For years, he sold locally. Most of his customers are familiar faces, in our usual currency and with the exact expectations. But one day, a traveller visited his workshop
What is the easiest way to move Hotmail emails to an IMAP server?
The easiest way to move Hotmail (Outlook.com) emails to an IMAP server is to add your Hotmail account directly to any email client that supports IMAP, then copy the messages across. This avoids paid tools and keeps the process simple. Fastest free method:
No feedback from Zoho Books regarding Yodlee feeds for Investec
I reported on 6 Feb 2025 that the Bank Feeds for Investec bank via Yodlee are not working. To date there has been no resolution. You are charging us for a suite of products where the functionality does not work. Please advise how you will refund me for
Turn off Knowlege Base Follow options and Follower lists
Is there a way to hide or turn off the option in the Knowledge Base for users to follow specific departments/categories/sections/articles? If not, is there a way to turn off the public list of followers for each of those things? Otherwise, customer names
Text snippet
There is a nice feature in Zoho Desk called Text Snippet. It allows you to insert a bit of text anywhere in a reply that you are typing. That would be nice to have that option in Zoho CRM as well when we compose an email. Moderation Update: We agree that
Service One. Bill Another. Zero Hassle.
In field service operations, one challenge often comes up: the person who needs the service isn’t always the one who pays for it. Think about tenants vs. property managers, corporate offices vs. their finance departments, or school buildings vs. central
Presenting ABM for Zoho CRM: Expand and retain your customers with precision
Picture this scenario: You're a growing SaaS company ready to launch a powerful business suite, and are looking to gain traction and momentum. But as a business with a tight budget, you know acquiring new customers is slow, expensive, and often delivers
Can Zoho Flows repeat Actions more than once?
I'm attempting to make an intentional Zoho Flow loop using the below layout. However, when "WithinLimit" condition is met, the program fails to execute the action "Get & Add Request Co..." again. Is this by design? Is Zoho Flows unable to repeat actions
Video interviews not compatible on mobile
Hi Zoho, The 2-way video interview feature you have added to Recruit would be great if the candidate was able to use the link to the video on a mobile phone, it doesn't work on Samsung browser, it doesn't work on chrome/firefox mobile version of browser,
Zoho Desk View Open Tickets and Open Shared Tickets
Hi, I would like to create a custom view so that an agent can view all the open tickets he has access to, including the shared tickets created by a different department. Currently my team has to swich between two views (Open Tickets and Shared Open Tickets).
Enviro Recycling EHF Fee's
I'm in Canada and each province has it's own recycling program with it's own fee's. There are numerous categories that electronics fall into and each category has a taxable rate applied to it. I need a way to be able to apply the correct rate depending
Zoho Desk API - Influence which layout is used
Hello, how can the ticket layout be changed using the API? I would like to choose the layout directly when creating the ticket. If this is not possible, my question would be how can I change it afterwards? Best regards, Sven
Has CSS Changed for Common Elements?
I noticed today that the standard title headings on my Forms and Reports display smaller in size than before. Google Dev Tools (attached), seems to confirm my suspicion. Something appears to be overriding the 1.375rem to 1.125rem. The font size on the
Text field alignment
Is it possible to align text in the text field? Or even better, is there a possibility to have a field which accepts only numbers?
email moderation issue when email is sent in the name of a mail group
Symptom: an email that is sent by a mail group moderator in the name of a moderated mail group is held back for approval. Reproduction: Create a moderated mail group with members and moderators. Allow that mails can be sent in the name of the group (extended
Pipeline in Custom Modules
I love the way the Sales Pipeline looks and functions with reports. I would like to add the save pipeline features and visualization to a custom module, however, I only see that these pipelines are only available for the Deals module. Is there a way to add pipelines to custom modules?
Tip #53- Remote Billing: Your End-to-End Billing Solution in Zoho Assist- 'Insider Insights'
Manual invoicing, after every remote support session, can be time-consuming and often error-prone. As an MSP, IT admin, or even a freelance support technician, it may become overwhelming to keep track of session time, service rates, and client invoices.
Record history and ticket interaction tab in contacts and accounts
Hello everyone! We have improved the History tab to help users trace updates in the interaction activity logs of the Contacts and Accounts detail pages in the Customer Module. This enhancement make it easier to get clear details about who made each update,
Is there an API to "File a Ticket" in Desk
Hi, Is there an API to "File a Ticket" in Desk to zoho projects?
Keyboard UX for Assemblies
The new Assembly module has a counter-intuitive behavior that ought to be corrected. When an Assembly is ready to be entered, there are two options given, the blue-highlighted "Assemble" and the gray "Save as Draft". This correctly implies that the normal
Why can't I see images uploaded by other users in the Library for Campaigns
We are several users of zoho Campaign. I have uploaded visuals of our company, but my team members can't see them.
zoho sheet stuck
I Need help. ZOHO sheets stuck on the loading screen. I've already deleted the system cache and cookies of my browser (google chrome) but it's still not opening.
Tip of the Week #78 – Cut response time with multichannel shared inboxes
If you’ve ever felt your team juggling between multiple email accounts, social pages, and chat apps just to reply to customers, you’re not alone. Managing conversations in multiple channels can quickly turn messy. Important messages across inboxes, replies
Need a way to run a client script longet than 10 seconds
By The Grace of G-D. Hi, Currently, Client Scripts are Timing out at 10 seconds. We have complex logics that needs more time. Can you add a feature request to increase the timeout?
FSM Improvement Idea - Show an Import button when there is no data
I am setting up FSM for a client and I noticed that there is no option to import data, see screenshot below. Even when you click Create Contact there is only an option to Import from Zoho Invoice. It is only after you add at lease 1 record that the Import
UI Improvement - Ability to Collapse Flow
The UI for Flow is generally pretty good. However, when multiple decision trees are used, the layout can get pretty convoluted and hard-to-follow (see one of my Flows below): In these cases, even the auto-arrange fails to make this something that a normal
Tasks Statuses
Hi, The task status "Completed" is a final status which closes the task. We need to have a status "Cancelled". However, when the status is set to "Cancelled", the task prompt still has a blue button to Close Task. When the customer clicks that and closes
Add Lookup Field in Tasks Module
Hello, I have a need to add a Lookup field in addition to the ones that are already there in the Tasks module. I've seen this thread and so understand that the reason lookup fields may not be part of it is that there are already links to the tables (
Create New Tasks Layout in CRM
I am able to do this in Leads, Contacts, Meetings, Calls - every other module, but cannot create a new layout in tasks. I have the appropriate access but it's simply not appearing as an option. Only "Standard" option shows. Please help!
Using IMAP configuration for shared email inboxes
Our customer service team utilizes shared email boxes to allow multiple people to view and handle incoming customer requests. For example, the customer sends an email to info@xxxx.com and multiple people can view it and handle the request. How can I configure
Auto-sync field of lookup value
This feature has been requested many times in the discussion Field of Lookup Announcement and this post aims to track it separately. At the moment the value of a 'field of lookup' is a snapshot but once the parent lookup field is updated the values diverge.
Zoho Recruit Subscription
Hello Zoho Recruit Team, Good day! I would like to inquire about your recruitment subscription plans and would also like to verify the current subscription our company is enrolled in under Zoho Recruit. Thank you, and I look forward to your response.
Is it possible to create a meeting in Zoho Crm which automatically creates a Google Meet link?
We are using Google's own "Zoho CRM for Google" integration and also Zoho's "Google Apps Sync" tools, but none of them provide us with the ability to create a meeting in Zoho CRM that then adds a Google Meet link into the meeting. Is this something that
Automatic Portal invite
We have numerous customers we move through a blueprint in deals, when they get to a certain point we need to give them portal access, how can this be done through deluge or a workflow? Latest Update (December 2025): The option to automate portal invitations
Marketer's Space: Why mobile optimization deserves a place in your email strategy
Hello Marketers, Welcome back to Marketer's Space! Today, we'll talk about the importance of creating mobile-friendly email designs. While mobile phones were once used only to make phone calls, today they're used for almost everything, including texting,
Next Page