This blog post will walk you through the process of fetching data from Webflow using Supabase. Webflow provides an API that you can use to access data from collections with a sync RPC call. However, to access the API, you need an API key. This post will show you how to store the API key securely using pgsodium and Supabase.
Additionally, it will guide you on how to fetch data from Webflow using Supabase with code snippets. You will learn how to use the supabase-js client library to call a Supabase stored procedure that retrieves data from Webflow. Lastly, the post includes examples of the response data that you can expect from Webflow. By following the instructions in this post, you can easily fetch data from Webflow and use it in your Supabase application.
Setting up Webflow access:
Go to your web flow dashboard, then click on Integrations, then scroll down to API Access and click on "Generate new API Token":
Then, copy the API key in a text file (for now):
Enabling extensions on Supabase:
Go to the Supabase dashboard and enable the following extensions:
HTTP
PGSODIUM
PG_NET
Storing the API key properly with pgsodium:
Please note that if you are running this in Supabase SQL Editor, then you'll need to run a command at each time eg `CREATE SCHEMA secrets;
`, click on Run
, then run the command below it:
-- Creating the schema to store this:
CREATE SCHEMA secrets;
-- Creating the table to store the API keys:
CREATE TABLE secrets.api_keys (
id uuid PRIMARY KEY NOT NULL DEFAULT uuid_generate_v4(),
key_id uuid NOT NULL REFERENCES pgsodium.key(id) DEFAULT (pgsodium.create_key()).id,
nonce bytea NOT NULL DEFAULT pgsodium.crypto_aead_det_noncegen(),
api_secret text NOT NULL DEFAULT 'undefined'::text,
name text NOT NULL
);
-- Setting up pg_sodium for the table:
SECURITY LABEL FOR pgsodium
ON COLUMN secrets.api_keys.api_secret
IS 'ENCRYPT WITH KEY COLUMN key_id NONCE nonce ASSOCIATED id';
-- Adding the secrets to the table:
INSERT INTO secrets.api_keys(name, api_secret) values ('webflowapi','06cfa959a211d3314ad0cf796105b16363d420a2dab07d922035914739a40079');
Allowing the postgres
user to access the pgsodium
:
This is not necessary if you don't plan on using this from Supabase dashboard.
GRANT pgsodium_keyiduser TO "postgres";
GRANT USAGE ON SCHEMA pgsodium TO "postgres";
You can access this secret using the view that was automatically created for it:
select decrypted_api_secret from secrets.decrypted_api_keys where name = 'webflowapi';
Fetching data from webflow:
To assist with requesting passing the authorization/bearer tokens, I've created a helper function that extends http_get just for this very specific request.
CREATE OR REPLACE FUNCTION http_get_with_auth(url_address text, bearer text)
RETURNS TABLE (__status text, __content jsonb)
SECURITY DEFINER
SET search_path = public, extensions, secrets AS
$BODY$
DECLARE
full_bearer text := 'Bearer ' || bearer;
BEGIN
RETURN QUERY SELECT status::text, content::jsonb
FROM http((
'GET',
url_address,
ARRAY[http_header('Authorization',full_bearer)],
NULL,
NULL
)::http_request);
END;
$BODY$
LANGUAGE plpgsql volatile;
Now, we take advantage of the helper function and call it from the function that fetches the data from Webflow:
CREATE OR REPLACE FUNCTION fetch_webflow(collection_id text, _offset bigint, _limit bigint)
RETURNS TABLE (status text, content jsonb)
SECURITY DEFINER
SET search_path = public, extensions, secrets AS
$BODY$
DECLARE
webflow_api text;
url_address text;
BEGIN
webflow_api := (select decrypted_api_secret from secrets.decrypted_api_keys where name = 'webflowapi');
url_address := format('https://api.webflow.com/collections/%s/items?offset=%s&limit=%s', collection_id, _offset::text, _limit::text);
RETURN QUERY (SELECT __status, __content FROM http_get_with_auth(url_address, webflow_api));
END;
$BODY$
LANGUAGE plpgsql volatile;
Getting the CollectionID from Webflow:
Open your Webflow dashboard and go to the designer tool:
Then, click on the collections, select the desired collection to see the ID:
It is possible to fetch collection items with Supabase:
Using the supabase-js client library:
const { data: ret, error } = await supabase
.rpc('fetch_webflow',
{
collection_id: '64275ecc5c4c4c51453539df',
_limit: 10,
_offset:1
});
if (ret[0]['status']=="200")
console.log(ret[0]['content']);
else{
console.log("Error: "+ret[0]['status']+" "+JSON.stringify(ret[0]['content']));
}
Examples of response:
200 with results
{
count: 2,
items: [
{
as: 'Maxime nihil molestiae rem est ipsam animi velit. Accusamus est placeat sapiente ver',
_id: '64275ed269d01c30c9fddc2f',
bar: 'https://wordpress.com',
_cid: '64275ecc5c4c4c51453539df',
name: 'Sapiente Id Quo Consequatur',
slug: 'sapiente-id-quo-consequatur',
_draft: false,
_archived: false,
'created-by': 'Person_64275647bde7a7c408785df6',
'created-on': '2023-03-31T22:29:38.963Z',
'updated-by': 'Person_64275647bde7a7c408785df6',
'updated-on': '2023-03-31T22:29:38.963Z',
'published-by': 'Person_64275647bde7a7c408785df6',
'published-on': '2023-03-31T22:30:45.459Z'
},
{
as: 'Enim cum molestiae distinctio porro nisi atque. Tenetur voluptas qui qui molestiae cum veniam. Facilis explicabo et harum ipsa dolore enim ',
_id: '64275ed269d01c83aafddc30',
bar: 'https://www.craigslist.org',
_cid: '64275ecc5c4c4c51453539df',
name: 'Nulla Minus',
slug: 'nulla-minus',
_draft: false,
_archived: false,
'created-by': 'Person_64275647bde7a7c408785df6',
'created-on': '2023-03-31T22:29:38.963Z',
'updated-by': 'Person_64275647bde7a7c408785df6',
'updated-on': '2023-03-31T22:29:38.963Z',
'published-by': 'Person_64275647bde7a7c408785df6',
'published-on': '2023-03-31T22:30:45.459Z'
}
],
limit: 2,
total: 10,
offset: 1
}
No more entries:
{ count: 0, items: [], limit: 2, total: 10, offset: 50 }
Collection ID error:
Error: 400 {"err":"ValidationError: Provided ID is invalid","msg":"Provided ID is invalid","code":400,"name":"ValidationError","path":"/collections/64275ecc5c4c4c51453539df_/items"}
Congratulations for following along in this guide. If you read this here, share this post on Twitter with the emoji of your favorite fruit. ๐