These integration guides are not official documentation and the Strapi Support Team will not provide assistance with them.
What Is Retool?
Retool is a low-code development platform for building internal applications through a web-based IDE with pre-built UI components and data source connectors. Instead of writing React components and managing state, developers use Retool's visual editor to connect databases and APIs directly to pre-built tables, forms, and charts.
For full-stack developers, Retool makes sense when building tools consumed internally rather than customer-facing applications. The trade-off is less control over UI details in exchange for dramatically faster development velocity.
Why Integrate Retool with Strapi
Connecting Retool to your Strapi backend accelerates internal tool development while maintaining the API-first architecture you've already built.
- Skip frontend scaffolding for admin interfaces. Strapi's auto-generated REST endpoints connect directly to Retool's pre-built components, eliminating the need to build React admin panels for content moderation or editorial workflows.
- Unify data across multiple sources. Editorial teams can use Retool to combine Strapi content with analytics or user data from other systems in a single interface.
- Implement granular access control. Strapi's API token system provides permission scoping at the content type level, working better than building custom permission logic in a one-off admin tool.
- Optimize query performance declaratively. Strapi's REST API supports selective field population and pagination through query parameters, which combined with Retool's query caching lets you handle production-scale datasets without custom optimization logic.
How to Integrate Retool with Strapi
The integration requires four configuration steps: generating a Strapi API token, creating a Retool REST API resource, building queries for CRUD operations, and optionally configuring GraphQL API access for more efficient data fetching.
Generate Strapi API Token
Navigate to Settings → API Tokens in your Strapi admin panel and click Create new API Token. Configure the following settings:
- Name:
retool-integration(or any descriptive identifier) - Token duration: Choose based on security requirements—unlimited for persistent integrations, or time-limited (7 days, 30 days, or 90 days) for temporary access.
- Token type: Pick Custom for granular control.
For custom tokens in Strapi, configure permissions directly in the API token settings where you can control access to specific API endpoints and CRUD operations per content type. If you're building a content moderation dashboard, create a custom token that allows GET and PUT operations on your articles endpoints while restricting DELETE operations.
This granular permission configuration prevents accidental data deletion through the Retool interface, aligning with security best practices documented in Strapi's API token configuration.
Copy the generated token immediately—Strapi displays it only once. Store it securely in a password manager or environment variable system for use in your Retool integration, following the security best practices outlined in Strapi's API token documentation.
Configure Retool REST API Resource
From your Retool dashboard, select Resources, then "Create new" → "REST API".
The Base URL should point to your Strapi API endpoint:
https://your-strapi-domain.com/apiThe /api prefix is mandatory in Strapi. This base URL gets prepended to all queries, so you'll reference content types with just their plural name (like /articles rather than the full path).
For authentication, pick Bearer token from the authentication type dropdown (or alternatively, use Header and configure with Key: Authorization and Value: Bearer YOUR_STRAPI_API_TOKEN). Paste your Strapi API token in the Token field. According to Retool's authentication documentation, Retool automatically sanitizes headers containing sensitive keys like authorization, key, or password to prevent credential exposure in logs.
Set these additional options in the Advanced section:
- Headers: Include
Content-Type: application/jsonif not automatically added. Headers can be configured at both resource level (global headers for all queries) and query-specific levels. According to Retool's REST API documentation, Retool automatically sanitizes headers containing sensitive keys to prevent credential exposure in logs. - Cookies: Configure based on your authentication method. For cookie-based authentication patterns, Retool's authentication guide supports session cookies through HTTPOnly cookie configuration tied to user sessions. If implementing the Double Cookie Submit Pattern, prefix headers with
COOKIE_to send cookies to your backend (e.g.,COOKIE_sessionId). - Allow self-signed certificates: Enable only for local development with self-signed SSL certificates. This setting should be used carefully and disabled in production environments. For production deployments, always use valid SSL certificates signed by recognized certificate authorities.
Save the resource configuration. Retool validates the connection by testing the authentication headers and verifying that your Strapi instance is reachable.
If your Strapi instance is unreachable or authentication fails, Retool's REST API documentation notes that Retool displays an error in the debugging console with the corresponding status code—such as 401 Unauthorized for invalid tokens, 403 Forbidden for insufficient permissions, or 404 Not Found for incorrect endpoints. Review the request details in the debugging console to diagnose connectivity and authentication issues.
Build Read Queries
Add a Table component to a new Retool app. Within the query editor, select your Strapi resource and set:
Method: GET
URL: /articlesThis fetches all articles. According to Strapi's REST API documentation, the response structure follows Strapi's standard format with data wrapped in a data array and metadata in a meta object. This response structure applies consistently across all REST API endpoints.
Include query parameters for filtering and pagination. In the Params tab:
filters[publishedAt][$null]: true
pagination[page]: {{ table1.pageIndex + 1 }}
pagination[pageSize]: 25
sort: createdAt:descThis query returns unpublished articles (using publicationState=preview to fetch draft content), implements pagination tied to the table component's state, and sorts by creation date descending.
For relation population, use selective field specification rather than wildcards. Use populate[relationName][fields][0]=fieldName to fetch only specific fields from related content types, avoiding the performance impact of populate=* which fetches all relations recursively and can significantly impact performance.
This approach enables developers to request only the specific fields needed for their Retool interface while maintaining production-scale performance.
To implement selective population of author fields in a Strapi REST API query, use the following query parameter syntax:
GET /api/articles?populate[author][fields][0]=name&populate[author][fields][1]=email&populate[categories][fields][0]=nameThis pattern allows developers to fetch specific fields from related content types without requesting all available attributes, which optimizes payload size and improves performance. In this example, the query fetches article data while populating only the name field from the author relation and the name field from categories, avoiding unnecessary data transfer.
Avoid populate=* in production—according to GitHub issue #13288, this recursively fetches all first-level relations and causes significant performance degradation on content types with deep relationship graphs. Anyone who's debugged performance issues in production knows that populate=* is the first culprit.
Bind the query result to your table by setting the Data property:
{{ strapiArticles.data.data }}The double .data accessor reflects Strapi's response structure where the HTTP response has a data property containing the actual array of entries.
Implement Create Operations
Build a Form component containing input fields that match your Strapi content type schema. Configure a new query:
Method: POST
URL: /articles
Body (JSON):
{
"data": {
"title": {{ titleInput.value }},
"content": {{ contentInput.value }},
"publishedAt": null
}
}According to Strapi's REST API documentation, the data wrapper is required for all Strapi write operations (POST and PUT requests). Template syntax ({{ }}) binds form component values dynamically.
Place a Button component and configure its click event handler to trigger this query. Set success and error handlers in the query's Advanced tab → Error handlers section to provide user-friendly notifications based on status codes.
// Success handler
utils.showNotification({
title: "Article created",
description: `Created "${titleInput.value}"`,
notificationType: "success"
});
articlesTable.trigger(); // Refresh the table
form1.reset(); // Clear form inputs// Error handler for create article mutation
if (createArticle.error) {
if (createArticle.error.status === 400) {
utils.showNotification({
title: "Validation Error",
description: "Check that all required fields are included in the data wrapper",
notificationType: "error"
});
}
}Implement Update Operations
To implement update workflows with Retool and Strapi, configure REST API queries following the documented patterns. Create a PUT request to your Strapi endpoint (e.g., /api/articles/{{ table1.selectedRow.id }}) with the updated data in JSON format. This query can be triggered by button components or form submissions to update content entries.
Create an update query:
Method: PUT
URL: /articles/{{ table1.selectedRow.id }}
Body (JSON):
{
"data": {
"title": {{ editTitleInput.value }},
"publishedAt": {{ publishCheckbox.value ? new Date().toISOString() : null }}
}
}Note the use of documentId rather than a numeric id—according to Strapi's REST API reference, Strapi 5 uses document-based identifiers, which is a breaking change from Strapi v4. If you're on Strapi v4, use {{ table1.selectedRow.id }} instead.
The publish checkbox demonstrates conditional logic: when checked, it sets the current timestamp; when unchecked, it leaves the article in draft state.
Handle Filtering and Search
Insert a Text Input component above your table for search functionality. Update your articles query with dynamic filtering using Strapi's filtering operators. Configure the query URL with the $containsi operator for case-insensitive search:
/articles?filters[title][$containsi]={{ searchInput.value }}&pagination[page]=1&pagination[pageSize]=25This approach uses Strapi's comparison operators to search the title field against the text input value in real-time, enabling efficient server-side filtering that reduces data transfer and improves performance compared to client-side filtering.
Method: GET
URL: /articles
Params:
filters[title][$containsi]: {{ searchInput.value }}
filters[publishedAt][$null]: {{ showDraftsCheckbox.value }}
pagination[page]: {{ table1.pageIndex + 1 }}
pagination[pageSize]: 10According to Strapi's REST API parameters documentation, the $containsi operator performs case-insensitive substring matching. Set the query to run on component value changes by configuring Run query on page load and adding searchInput to the Watched values array. The $containsi operator is available as a case-insensitive filtering operator alongside case-sensitive alternatives like $contains.
For multi-field search, you can use a JavaScript transformer to structure complex filters:
const searchTerm = searchInput.value;
const filters = {};
if (searchTerm) {
filters.$or = [
{ title: { $containsi: searchTerm } },
{ content: { $containsi: searchTerm } }
];
}
return { filters };Pass this transformer result to your query parameters using JavaScript syntax to reference the transformer's output data structure directly in your query configuration.
Optimize Performance for Production
Configure query caching for read-heavy operations. In your GET query settings, enable Cache query response and set the duration based on content update frequency (for example, 5 minutes for frequently updated content, 1 hour for relatively static data).
Implement pagination metadata display by placing a Text component showing:
`Showing ${table1.pageSize * table1.pageIndex + 1}-${Math.min(table1.pageSize * (table1.pageIndex + 1), strapiArticles.data.meta.pagination.total)} of ${strapiArticles.data.meta.pagination.total}`For large datasets, consider implementing offset-based pagination instead of page-based. According to Strapi's sort and pagination documentation, offset-based pagination uses pagination[start]=0&pagination[limit]=25 parameters, allowing more flexible data retrieval compared to page-based pagination which uses pagination[page]=1&pagination[pageSize]=25.
pagination[start]: {{ table1.pageIndex * table1.pageSize }}
pagination[limit]: {{ table1.pageSize }}Insert error handling for common scenarios. In your query's Advanced tab → Error handlers section:
if (query1.error) {
const status = query1.error?.status;
if (status === 401) {
utils.showNotification({
title: "Authentication Error",
description: "Please check your API token",
notificationType: "error"
});
} else if (status === 403) {
utils.showNotification({
title: "Permission denied",
description: "Token lacks required permissions",
notificationType: "error"
});
} else if (status === 404) {
utils.showNotification({
title: "Not found",
description: "Check the API endpoint configuration",
notificationType: "error"
});
}
}This error handling provides actionable feedback when authentication tokens expire, permissions are misconfigured, incorrect endpoints are used, or validation errors occur in request body formatting.
Project Example: Content Moderation Dashboard
This example demonstrates how to combine documented Strapi API features with Retool components for a content moderation workflow. The implementation uses Strapi's filtering, relational population, and draft/publish system alongside Retool's table and form components to build an editorial interface without custom frontend development.
Query Configuration for Moderation Workflows
A content moderation dashboard typically displays articles filtered by publication status, allowing editors to review unpublished content and transition it to published state. Using Strapi's documented filtering operators, you can query draft articles:
Method: GET
URL: /articles
Params:
filters[publishedAt][$null]: true
populate[author][fields][0]: username
populate[author][fields][1]: email
fields[0]: title
fields[1]: content
fields[2]: createdAt
pagination[page]: {{ table1.pageIndex + 1 }}
pagination[pageSize]: 25
sort: createdAt:descThis query pattern demonstrates complex filtering combining publication status checks with selective population of author relationships. According to Strapi's REST API documentation, the filters[publishedAt][$null]=true parameter returns only draft content, while the populate[author][fields] syntax fetches specific fields from the related author without retrieving all author attributes.
Bind this query to a Table component in Retool, setting the data property to {{ articlesQuery.data.data }}. Add columns for title, excerpt, creation date, and assigned author email. The table's built-in pagination automatically works with the query's pagination parameters.
Implementing Status Updates
Publishing workflow actions use Strapi's documented PUT operation with the required data wrapper. When an editor clicks an "Approve" or "Publish" button in your Retool table, trigger this query:
Method: PUT
URL: /articles/{{ table1.selectedRow.documentId }}
Body (JSON):
{
"data": {
"publishedAt": {{ new Date().toISOString() }}
}
}Note the use of documentId for Strapi 5—if you're on Strapi v4, use id instead.
For multi-step approval workflows, you can add custom fields to your Strapi content type (such as a reviewStatus field) and filter based on those fields using the same filtering syntax:
filters[reviewStatus][$eq]: pendingThis enables progressive content states (draft → review → approved → published) using Strapi's flexible filtering capabilities combined with Retool's conditional button visibility.
Bulk Operations with JavaScript Transformers
For bulk status updates, use Retool's JavaScript query capabilities to process multiple selected rows. The following pattern uses documented API operations in sequence:
const selectedArticles = articlesTable.selectedRows;
for (const article of selectedArticles) {
await strapiResource.trigger({
method: 'PUT',
url: `/articles/${article.documentId}`,
body: {
data: {
publishedAt: new Date().toISOString()
}
}
});
}
utils.showNotification({
title: "Bulk update complete",
description: `Published ${selectedArticles.length} articles`,
notificationType: "success"
});
articlesTable.trigger(); // Refresh tableThis implementation processes updates sequentially to avoid overwhelming Strapi's connection pool. For production use, consider adding error handling within the loop to track which updates succeed and which fail.
Content Preview with Relational Data
Display selected article details in a Container component using Retool's conditional visibility:
// Visibility condition
{{ articlesTable.selectedRow !== null }}Within the container, place Text components bound to the selected row's attributes:
// Title display
{{ articlesTable.selectedRow.title }}
// Author display
{{ articlesTable.selectedRow.author.username }}
// Content preview
{{ articlesTable.selectedRow.content.substring(0, 500) + '...' }}This pattern uses the relational data fetched through selective population in the original query, avoiding additional API calls for detail views.
Search and Filter Controls
Add a Text Input component for title search using Strapi's case-insensitive filtering:
Method: GET
URL: /articles
Params:
filters[title][$containsi]: {{ searchInput.value }}
filters[publishedAt][$null]: true
pagination[page]: 1
pagination[pageSize]: 25According to Strapi's REST API parameters documentation, the $containsi operator performs case-insensitive substring matching on the title field. Configure the query to run automatically when searchInput.value changes by adding it to the query's watched values.
For date range filtering on submission dates, use Strapi's comparison operators:
filters[createdAt][$gte]: {{ dateRangePicker.value.start }}
filters[createdAt][$lte]: {{ dateRangePicker.value.end }}Performance Optimization
For moderation dashboards displaying large volumes of articles, implement these documented optimization techniques:
Selective population instead of populate=*: According to GitHub issue #13288, using wildcard population causes severe performance degradation. Always specify exact relations and fields:
populate[author][fields][0]: username
populate[categories][fields][0]: namePagination with reasonable page sizes: According to Strapi's sort and pagination documentation, Strapi's default pagination is 25 items per page with a maximum of 100. Use the default or lower values for optimal performance:
pagination[pageSize]: 25Query caching in Retool: Enable caching for read-heavy queries with cache duration based on content update frequency. Access this in your query's settings panel.
Field selection: Use fields[0]=title&fields[1]=content to limit response payloads to only required attributes, reducing data transfer and improving response times.
This content moderation dashboard pattern demonstrates how documented Strapi API features (filtering, selective population, draft/publish system) combine with Retool's components (tables, forms, JavaScript transformers) to create functional internal tools without building custom frontend applications.
Strapi Open Office Hours
If you have any questions about Strapi 5 or just would like to stop by and say hi, you can join us at Strapi's Discord Open Office Hours, Monday through Friday, from 12:30 pm to 1:30 pm CST: Strapi Discord Open Office Hours.
For more details, visit the Strapi REST API documentation and Retool REST API connection guide.
Get Started in Minutes
npx create-strapi-app@latest in your terminal and follow our Quick Start Guide to build your first Strapi project.