Move your Airtable-powered no-code client sites to Duda

Liz Fedak
9 min readJan 7, 2022


If you haven’t heard of Duda yet, it’s a web design platform similar to WordPress, but built to help agencies scale and not worry about the headache of monthly maintenance of plugins thanks to it’s API, app store, and other awesome features. It has a built-in featured called Collections, which enable you to pull in data directly from external sources like Airtable, Google Sheets, and databases. This article covers some things you can make using Duda’s built-in custom widget builder to add awesome functionality–like sorting, searching, filtering, and more–to your client’s sites. So if you’re on the fence about moving from Softr or Wordpress, check this out and see how easy it can be to do it all in Duda.

How to add a collection to a Duda site

Adding a collection to a site is extremely easy! Open up your Duda site and open up the Content menu. Click on the Collections menu. Click the orange New Collection button. For this walkthrough, we’ll use Airtable, so click on the option to add a new Airtable collection.

To connect your Airtable collection, head to api.airtable.coma and open up the collection that you want to add. Once you’re on the API page specifically for that collection, you’ll be able to grab the target URL and the authentication token from the example curl request.

Click on the Authentication link to have the screen scroll to the exact spot where you can grab those values. Check the box where it says ‘show API key’. That’s a SECRET value, so don’t post it publicly. Then paste those right into the configuration menu back on the Duda site–don’t worry, it’s secure there and only used on the server side.

Once the site is able to make the API call, make sure the column names match the fields that are listed in the Duda collection panel. You’ll be able to modify the field type to accommodate for data types like images, links, rich text, and more.

Now that you’ve got your collection connected, you can start to display the data! Duda offers some built-in widgets, like the List widget and the Image gallery widget, which enable you to display your data immediately. You can also use Duda’s built-in “Dynamic Page” feature, which is basically a way to make a template that will then create a new page for each item in your collection. It’s pretty awesome!

How to add search, sorting, and filtering to your collection

Please note this specific setup requires JavaScript knowledge or help from your favorite Duda developer (that me!!) 🤣.

There are a ton of ways that you can build this in, but I personally love to use a library called list.js. It’s extremely well maintained and has all of the features built-in to code up custom widgets.

The library list.js works by using a string literal as a template for each of the items in your collection. So say that you wanted to add a blog page with custom search, you could make something like this work out very easily as a card template for each of the blog posts using the list.js library and some elbow grease:

Example card mock for a blog display widget powered by Airtable.

The template for this using list.js would basically take the HTML that you’d write if you were hand coding this card, and uses the class property to fill in the right data.

So for the image, you might do <img class=“image_prop”>. The image_prop part corresponds to a column in the Airtable table. So you might have an “Author image” column and that would be pulled in to correspond to the author image in the card.

This is the configuration that I use for one of my own widgets, which has search + filtering built-in:

let options = {
valueNames: [ 'title', 'resourceType', 'tags', 'tagsSORT', 'category', 'introText', 'authorLine',
{name: 'featuredImage', attr:'src'}, {name: 'authorLink_dup', attr:'href'}, {name: 'link', attr:'href'},
{name: 'link_dup', attr:'href'},{name: 'authorPageLink', attr:'href'},{name: 'agencyLink', attr:'href'}, 'timeToRead', 'agency', 'author','dateLine',
{name: 'profileImg', attr:'src'}],
item: `<li class="">
<div class="verticalFlexAuthorCard">

<a class="link"><img class='featuredImage'></a>
<div class="bodyTextPadding">
<a class="link_dup">
<div class='top'>
<div class="detailsLine"><span class='resourceType'></span> • <span class="timeToRead"></span></div>
<div class='title'></div>
<div class='introText'></div>
</div> <!--end of "top"-->

<div class="authorImgAndTextLine"><a class="authorPageLink"><img class="profileImg"></a>
<div class="nameDateWrap">
<div class="authorAgencyLine">
<a class="authorLink_dup"><span class='author'></span></a><span class='dividerBar'>&nbsp;|&nbsp; </span><a class="agencyLink"><span class='agency'></span</a>

<div class="dateLine"></div>
</div> <!-- end of nameDateWrap-->
</div> <!-- end of authorImgAndTextLine-->

</div> <!-- end of vertical flex -->
<div class='tags'></div>
page: 6,
pagination: [{
paginationClass: "customPagination",
left: 1,
right: 1,
innerWindow: 2

To specify your options in the widget builder, you’d add a List input. Let’s called the list input variable blogPosts. Under that list input, you’d add a field for each of the columns that you need to reference from your Airtable collection. I use the same variable names in the Content editor as I do in the list.js configuration so I can directly use the blogPosts object.

In the Duda widget builder, once you establish that “List” input, then in the JavaScript code, you can reference that list as an array of JavaScript objects. So to reference the blogPosts variable, you’d access it as data.config.blogPosts. So to establish your values in the list.js config using that collection, you’d add something like let values = data.config.blogPosts.

To configure the rest of the list.js object, you would have to write let blogPosts = new List(‘blogPostList’, options, values); If you need to do anything custom to your data outside of the scope of Airtable, you can do that too of course. For example, below I’d use that to ‘pretty print’ my numbers as a dollar value. You can do that in Airtable too, although Duda does not currently pull in the price formatting from Price fields. :( But luckily we can do it all with code!

let trucks = data.config.trucks;let values = => {
let tmp = obj;
tmp.styledprice = priceformatter(obj.Price);
return tmp;
function priceformatter(num) {
let str = String(num);
switch (str.length) {
case 4:
return `$${str.slice(0,1)},${str.slice(1)}`;
case 5:
return `$${str.slice(0,2)},${str.slice(2)}`;
case 6:
return `$${str.slice(0,3)},${str.slice(3)}`;
return `$${num}`;

For more info on using list.js for the basic configuration, read their documentation. They have a lot of examples too.

Adding functionality

Listing out the cards is great and all, but not the real value of using the library. What I love most about list.js is their built-in sorting, searching, and pagination!


In your HTML, add a search input inside of the same div as the one that you render your lists <ul> in:

<div id="resource-list">
<input type="search" tabindex="1" class="search">
<ul class="list"></ul>

(You’ll likely want to add a lot more later, but that’s the barebones HTML that you need to add.)


List.js has a sort option as well! As long as the button is inside of the outer div (for example, the id=resource-list div), then it will reference the field that matches the variable name of the data-sort attribute.

<button class="sort" data-sort="price">Sort by Price ⇅</button>

You can also code a default sort in using the List.js list API.

listObj.sort('name', { order: "asc" }); // Sorts the list in abc-order based on names


You just need to add a couple lines of code to add pagination! Here’s what you would add to the options object:

page: 6, // Add this line to say how many items per paginated page
pagination: [{
paginationClass: "customPagination",
left: 1,
right: 1,
innerWindow: 2
}] // this is OPTIONAL for adding a custom class name and configuration

Here’s what you would add to the HTML:

<ul class="customPagination"></ul>


My true love for list.js is from how EASY it is to filter all of the items. I can add as many multi-select fields as I want, and it can live update everything! To configure it, add your options to the HTML, then add some code that will watch for checkbox values changing. You’ll need to track all of these values with a custom class, but once that is built once, it’s extremely easy to make another/new version. I use this model to create multi-select options, single-select options like true/false, price ranges, and more. You can really do anything!

Then what you do is write your own JavaScript filter function. This is where you really do need some JavaScript experience. But basically, what you’re going to do is use the built-in array filter method and add in conditions that will say if an item should be visible or hidden.

Then you can just call the sort function from the list.js API using those conditions. That is why I am so in love with this library!

Here’s an example of how I keep track of what data points are valid. Ignore the ‘autocomplete’ references — that’s for another library.

$("input.desktop:checkbox").change(function() {
let key = this.value;
let category = this.getAttribute('data-category');
if (this.checked) {
filterObject.addToCategory(category, key);
} else {
filterObject.removeFromCategory(category, key);
function clearAll() {
const filters = {
clearAll() {
let checkBoxes = document.querySelectorAll('input[type=checkbox]:checked');
checkBoxes.forEach(x => {
x.checked = false;
filterObject = filterMaker();

document.getElementById('autoComplete').value = "";
// document.getElementById('selectionClear').innerHTML = "";
document.querySelector(".selection").innerHTML = "";
// Replace Input value with the selected value
autoCompleteJS.input.value = "";
categoryIsTrue(category, item) {
// item is in key array or key array is empty
if (filterObject[category].length === 0) {
return true
} else if (filterObject[category].includes(item.values()[category])) {
return true
} else {
return false
addToCategory(category, value) {
removeFromCategory(category, value) {
filterObject[category].splice(filterObject[category].indexOf(value), 1);
function filterMaker() {
let count = itemsList.length;
return Object.assign({
resourceType: [],
agency: [],
author: []
}, filters);
let filterObject = filterMaker();$( "#clearFilters" ).click(function() {
function filterItems() {itemsList.filter(function(item) {
if (filterObject.categoryIsTrue('resourceType', item) &&
filterObject.categoryIsTrue('agency', item) &&
filterObject.categoryIsTrue('author', item)) {
return true
} else {
return false
let numItems = itemsList.matchingItems.length;

That’s about it! Leave some comments and let me know how you’ll implement this! And of course reach out to me if you need help setting this up or need to hire someone to do it for you:)


This widget uses the autocomplete library to to predict the search based on available results.

I was then able to recreate the widget VERY quickly for a slightly altered design:

This one is simplified but has some custom checkbox designs:

This one uses a splash of jQuery to easily toggle the search criteria to be opened and closed:

These were all built using list.js. I hope you fall in love with this library too and see how you can move more of your sites over to Duda!

If you have any questions, drop them in a comment below or book a meeting here if you want me to take a look at your JS–just drop it in a Codepen.

If you need hired help, you can also reach out to hire an expert in the Duda Community in the Duda Experts marketplace. My company, Widget Pro, is part of the Duda Expert program and would love to help you get started with Duda. There are also lots of other really awesome developers in the directory!



Liz Fedak

Journalist and endlessly curious person. One half of @hatchbeat.