Add Scheduled Tasks & Automations to Any Web App With Supabase
Run recurring tasks automatically with Supabase Cron, from database updates to scheduled Edge Functions and external API workflows.
Roadmap & Resources
Define Your Scheduled Task
Supabase runs your task on a schedule inside your project, so it keeps working when every browser is closed and nobody is signed in. Your AI assistant picks one of two ways to run it:
Two ways to run a scheduled task
Database job
Cron runs a database function. For expiring records, cleanups and status changes.
Server job
Cron calls an Edge Function. For emails, outside APIs, AI tasks and multi-step logic.
Describe Your Scheduled Task
Define Your Scheduled Task
Describe what should happen automatically and when, and let AI turn it into a clear task summary before anything is built.
I want to add a scheduled task to this existing web app, so something happens automatically on a schedule — on the server, even when nobody has the app open. The app uses Supabase. What should happen automatically: {{TASK}} How often it should run: {{FREQUENCY}} When it should run: {{RUN_TIME}} The timezone that time is in: {{TIMEZONE}} Which records or users it should affect: {{TARGET}} When there is nothing to process: {{EMPTY_RUN}} Outside services it needs: {{EXTERNAL}} Do not change any files, and do not change the database. This task only defines the scheduled task. Look through the project only enough to use its real names — the actual tables, features and statuses, such as appointments or subscriptions — so the summary describes this app rather than a generic example. Do not audit the code or the database yet. That is the next step. Turn my answers into a short summary with exactly these parts: 1. Task: what happens on each run, in one or two sentences 2. Schedule: in plain words, including the timezone — for example Every day at 09:00 Morocco time 3. Target: exactly which records or users each run affects, and which it must never touch 4. Done marker: how the app will know a record has already been handled, so a second run never repeats it — for example a reminded_at time or a status change 5. Nothing to process: what a run does when it finds no work 6. Expected result: what I should be able to see after a successful run 7. Likely type: a database job, if the work only reads and changes Supabase data, or a server job, if it needs an outside service, secrets or multi-step server logic. This is a first guess — the next step confirms it. Rules: - The schedule must run on the server. Never use browser timers, setInterval, setTimeout, localStorage or anything that only works while someone has the app open. - Suggest the least frequent schedule that still does the job. Do not run every minute when once a day is enough. - If my answers conflict or leave a gap — especially the timezone or which records to touch — ask me instead of guessing. Finish with the summary as a compact list I can copy and keep. Then wait for my approval. Do not implement anything.
What should happen automatically?
Find confirmed appointments happening tomorrow and send each customer a reminder.
How often should it run?
When should it run?
At 09:00 — or Mondays at 08:00
Which timezone is that time in?
Morocco time (Africa/Casablanca)
Which records or users should it affect?
Confirmed appointments starting tomorrow that have not had a reminder yet.
What if there is nothing to process?
Does it need an outside service?
Adjust the summary with the AI until it matches what you want. Keep this conversation open — every later prompt builds on the approved summary. If you ever start a new conversation, paste the summary in first.
Approve the Task Summary
You should now know what runs, when it runs and which records it changes.
The summary says exactly what happens on each run
The schedule names the time and the timezone I mean
It names the records the task changes — and the ones it must never touch
I saved a copy of the final summary
Check Your Web App
Inspect Your Project
Nothing changes in this step. The AI maps what already exists and picks the simplest way to run your task.
Check the Web App
Let AI inspect your Supabase setup, tables, security rules, functions and integrations, then choose a database function or an Edge Function before anything changes.
Using the scheduled task summary we just approved, inspect this project before anything is built. Do not change any files or the database in this task. If the approved summary is not in this conversation, STOP and ask me to paste it. Find and report: - How Supabase is set up: the client, the environment variables, and whether the project keeps database changes in migration files - The tables and columns the task reads or changes, and how the app marks those records today — statuses, dates, flags - How this action happens today, if it does at all: a button, an admin page, a script, server code or a database function - Existing database functions, triggers, Edge Functions and server routes related to the task - Existing email, notification, payment or outside-API integrations the task could reuse - Any scheduled logic that already exists: Supabase Cron jobs, other cron services, or browser timers standing in for a schedule - The secret and environment variable NAMES the task would need — never their values - How the app stores and compares dates and times, and which timezone it assumes - What marks a record as still waiting to be processed, and what would mark it as done - Whether a Supabase Secret or service-role key appears anywhere in browser code Do NOT assume the SQL in this repository matches what is live in Supabase. Functions, policies and jobs are often changed directly in the Supabase Dashboard. Give me ONE read-only SQL query to run in the Supabase SQL Editor. For the relevant tables only, it should report: - Their columns, and whether Row Level Security is enabled - Each policy's name, command, roles, USING and WITH CHECK expressions - Existing triggers, and the functions they call - Existing database functions related to the task, whether each is security definer, and who may execute it - Whether the pg_cron and pg_net extensions are enabled - Existing Cron jobs — name, schedule and whether each is active — if pg_cron is enabled - The names of existing Vault secrets, never their values, if Vault is in use The query must read metadata only. Do not select or print any user rows or secret values. Do not modify anything. If a part cannot run yet — for example because pg_cron is not enabled — leave it out and tell me, rather than letting the query fail. I will run the query and paste the result back into this conversation. Then give me a short plan: 1. What already exists 2. What can be reused: existing functions or integrations the task should call instead of rebuilding them 3. Recommended path, and why — exactly one of: - Database function: the task only reads and changes Supabase data. Cron calls the function directly. - Edge Function: the task needs an outside service, secrets or multi-step server logic. Cron calls the function over HTTP. 4. Required changes: database changes, and any function or code changes 5. The safest schedule: the plain-language schedule, the cron expression in UTC, and what that means in my timezone — including any daylight-saving shift 6. Implementation plan: the smallest reliable set of changes, in order Rules for the plan: - Use the simplest path that works. Do not add an Edge Function when a database function can do the job, and do not force outside API calls or secrets into database SQL. - If the app already has a secure way to do this action — for example a function that sends a reminder — reuse it. Do not create a second implementation. - Never schedule anything in the browser. - Keep existing security rules working. Do not loosen any of them. - If the task looks too large or too long-running for one scheduled run, say so now. Wait for my approval before changing anything.
Run the read-only SQL it gives you in the Supabase SQL Editor, then paste the result back into the same conversation.
Approve the Plan
Approve the plan once it picks one path and reuses what your app already has.
The plan picks one path — a database function or an Edge Function — and says why
It reuses what my app already has instead of building it twice
It shows the schedule in UTC and in my own timezone
It lists exactly which records the task will change
Build the Scheduled Action
Build the Action
Build the work itself first. Cron is connected only after it passes a run by hand.
Build the Scheduled Action
Build the database function or Edge Function your task runs — safe to run twice, safe when something fails, and ready for one manual test.
Now build the action the scheduled task will run, following the approved plan and its recommended path. Do not schedule it yet — that comes after a manual test. If the approved summary or the plan is not in this conversation, STOP and ask me for them. If the plan found an existing function or integration that already does this work, call it or extend it instead of building a second version. If the path is a database function: - Put the work in one database function with a clear name, such as expire_old_bookings - Change only the records the summary targets, found with a precise condition — never a broad update across the whole table - Where the table can grow, make that condition use indexed columns, so a run does not scan everything - Take no input from the browser. The function works out what to process from the database itself. - Cron runs it as the database owner, so it does not need security definer. If it genuinely needs it, give it a fixed, empty search_path. - Revoke execute from public, anon and authenticated, or keep it in a schema the API does not expose, so nobody can call it from the browser - Return a small summary, such as how many records it processed If the path is an Edge Function: - Create or extend one Edge Function with a clear name, such as send-daily-reminders - Read every secret — outside API keys, the Supabase Secret key — from Supabase project secrets. Never put a secret in frontend code, in the repository or in a response. - Use the server-side Supabase client inside the function, never one the browser could use - Find the records to process in the database from the summary's target, never from input a caller sends - Reject every caller except the trusted scheduler. Follow Supabase's current recommended pattern for functions called by Cron — for example accepting only a Supabase secret key sent in the apikey header. Never rely on the publishable or anon key alone: it is public. - Process a limited batch per run, such as 50 records, so each run finishes well within Edge Function time limits. The next run picks up anything left over. - Return a small summary: how many records it found, processed, skipped and failed For both paths — safe to run twice: - Use the done marker from the summary: a timestamp such as reminded_at or processed_at, a status change, or a unique constraint on a reference id — whichever is simplest for this data - Select only records that are not done yet, so a second run does nothing new - Where two runs could overlap, claim records atomically — for example with select ... for update skip locked, or an update that claims the rows and returns them — so two runs never take the same record - Do not over-engineer this. If one simple condition prevents repeats, use it. For both paths — safe when something fails: - Mark a record done only AFTER its important action succeeded — the email was accepted, the API call returned success, the update committed - If the action fails, leave the record unprocessed so a later run can try again, and log what failed without secrets or personal data - If the task sends messages, charges money or deletes data, add a dry-run option that only reports what it would process, and use it first Then give me one way to run it ONCE by hand, before any schedule exists: - Database function: one select statement to run in the Supabase SQL Editor - Edge Function: a one-off call from a local terminal, or from the function's test panel in the Supabase Dashboard, using the secret key from my local, uncommitted environment. Never a call from browser code. Give me: 1. What you built or reused, and where 2. Any SQL to run in the Supabase SQL Editor, in ONE block written so it is safe to run again 3. The exact manual run, and what its result should show If this project keeps database changes in migration files, also save the same SQL as a new migration. Deploy the Edge Function if the path uses one, and run the project's build afterwards.
Read the list of what the AI built or reused Run any SQL once in the Supabase SQL Editor Let the AI finish the code changes, the deploy and the build
Expected result
The action exists as one database function or one Edge Function, and the AI has given you the exact command to run it once by hand.
Run It Once by Hand
Prove the action does the right thing before any schedule depends on it.
Start with test records
This run works on your real tables. If the task sends messages, charges money or deletes data, use test records or the dry run first, and check the result before running it on everything.
Add one test record the task should process and one it must skip — ask the AI for the SQL if you need it Run the manual command the AI gave you Check both records in the Supabase Table Editor Run the command a second time
Expected result
The first run handles the test record and leaves the other one untouched. The second run finds nothing new to do.
Schedule It With Supabase Cron
Turn On Supabase Cron
Supabase Cron runs jobs inside your database, so they keep running when nobody has the app open.
Integrations Cron
Enable pg_cron there if it is not enabled yet.
Database Extensions
Edge Function path only: search for pg_net and enable it. Cron uses it to call your function.
Expected result
The Cron page shows Cron as enabled and opens its Jobs tab without errors.
Create the Cron Job
One clearly named job, on the least frequent schedule that does the job, with the time converted to UTC.
Schedule the Task
Turn your schedule into a UTC cron expression and create one clearly named Supabase Cron job that calls your function securely.
Now schedule the action with Supabase Cron, following the approved plan. The manual run has already worked. If the approved summary, the plan or the manual run result is not in this conversation, STOP and ask me for them. Schedule: - Convert the summary's schedule into a cron expression. Supabase Cron runs in UTC (GMT), so convert my local time to UTC — never assume my timezone matches the database. - Show the expression next to what it means in UTC and in my timezone, for example: 0 8 * * * — 08:00 UTC — 09:00 Morocco time - If my timezone changes its clocks for daylight saving, tell me in one sentence what happens to the local run time. If the exact local time matters, choose the simplest maintainable fix — for example run hourly and let the action work only in the right local hour, relying on the done marker so it acts once. Otherwise, accept the one-hour shift. - Use the least frequent schedule that still does the job. Do not run every minute when once a day or once an hour is enough. Name the job after what it does, clearly and permanently, for example send-daily-booking-reminders. Never a vague name such as job1. If the path is a database function: - Schedule a call to the function, for example: select cron.schedule('expire-old-bookings', '<cron expression>', 'select expire_old_bookings()') If the path is an Edge Function, follow Supabase's current pattern for scheduled Edge Functions: - The job calls the function with net.http_post from pg_net - The project URL and the key the function accepts are stored in Supabase Vault, and the job reads them from vault.decrypted_secrets each time it runs. Never write the key in plain text in the job command, in a migration file or in frontend code. - Send the key exactly the way the function checks it — for example the Supabase secret key in the apikey header - Give the request a timeout long enough for one batch to finish Scheduling the same job name again must update the existing job, never create a second one. Give me: 1. The job name, the cron expression, and what it means in UTC and in my timezone 2. ONE SQL block to run in the Supabase SQL Editor. Put any secret value in a clearly marked placeholder that I replace in the SQL Editor myself — never ask me to paste a secret key into this conversation. 3. What I should see on the Cron page afterwards If this project keeps database changes in migration files, save the job as a migration too, but with no secret values in it — the Vault secrets are created once, by hand.
Paste secret keys only into the SQL Editor
If your job calls an Edge Function, the key it stores in Vault can bypass your database's security rules. Replace the placeholder in the SQL Editor yourself — never paste the key into the AI conversation, a migration file or frontend code.
Read the job name and its schedule in UTC and in your own time Open the SQL Editor in your Supabase project Paste the SQL, replace any placeholder with the real value, and run it once Open the Jobs tab on the Cron page
Expected result
The Cron job now appears as active in Supabase with the intended schedule.
Confirm the Schedule
A quick check on the Jobs tab before you rely on it.
The job has a clear name that says what it does
Its schedule matches my local time once converted from UTC
The job is active
No secret key appears in plain text in the job's command
Make the Automation Reliable
Handle Empty, Repeated and Failed Runs
What separates a real scheduled system from a demo: nothing to do, a second run, and a run where something breaks.
Make the Task Reliable
Check that empty runs, repeated runs, partial failures and outside-service errors are all handled — and add only what is missing.
Now review and harden the scheduled task for the situations a real schedule meets. Inspect what we built first, and change only what is missing. If the approved plan is not in this conversation, STOP and ask me for it. Check and, where needed, fix: - Empty runs: when nothing needs processing, the run finishes cleanly and reports zero. No fake work, and not an error. - Repeated runs: running twice, or two runs overlapping, never repeats completed work. The done marker and the selection condition from the build step must guarantee it. - Partial failures: when one record fails, the others still complete, the failed one is not marked done, and a later run can try it again. If one record can fail every time, make sure it cannot block the others or break every run — for example by counting attempts and skipping it after a few. - Outside-service failures (Edge Function path): catch errors and timeouts from the outside service, log the record id and the error without secrets or personal data, and leave the record for the next run. Never retry in a tight loop inside one run. - Bounded work: each run processes a limited batch and finishes well within its time limits. The next run picks up the rest. - Size: if the real workload is too large or too slow for scheduled runs like these — for example thousands of slow API calls per run — tell me plainly. Recommend a queue or a separate worker only if the project genuinely needs one, and do not build one otherwise. Keep it the smallest reliable version. Do not build a retry platform, a job dashboard or a queue unless the checks above show the project needs it. Give me: 1. What was already handled, and what you changed 2. Any SQL to run in the Supabase SQL Editor, in ONE block written so it is safe to run again 3. Anything I should know about the limits of this task If this project keeps database changes in migration files, also save the same SQL as a new migration. Deploy the Edge Function again if you changed it, and run the project's build afterwards.
Expected result
The AI confirms each case is handled, and any fix is run, saved and deployed.
Lock Down Who Can Run It
Only the scheduler may trigger the task's privileged work — never someone using your app.
Check Who Can Run the Task
Make sure nobody using your app can call the scheduled function, steer it into other data or see the keys it runs with.
Now make sure only the scheduler — and nobody using the app — can run the task's privileged work. Check and, where needed, fix: - Database function path: anon and authenticated cannot execute the function, and it cannot be called through the API. Revoke execute from public, anon and authenticated, or move it to a schema the API does not expose. - Edge Function path: the function refuses every caller except the trusted scheduler. A request with no key, with the publishable or anon key, or with a signed-in user's token is rejected before any work happens. - The function accepts no input that changes which records it touches, so even a trusted call cannot be steered into other data - Elevated rights — the Supabase Secret or service-role key, security definer — stay inside the function and never reach the browser, the repository or a log - The job's command shows no secret in plain text, and the Vault secrets are readable only by trusted database roles Give me: 1. Each check, and whether it passed or what you fixed 2. Any SQL to run in the Supabase SQL Editor, in ONE block written so it is safe to run again If this project keeps database changes in migration files, also save the same SQL as a new migration. Deploy the Edge Function again if you changed it.
Expected result
Every check passes: the scheduler can run the task, and nobody using your app can.
Test & Monitor the Scheduled Task
Test the Scheduled Run
A short test schedule proves the whole chain now — no waiting until tomorrow.
Test the Scheduled Task
Use test records and a temporary short schedule to prove the job runs, touches only the right records, never repeats work and fails safely.
Help me prove the scheduled task works end to end before I rely on it. Do not wait for the real schedule. If the job name and the final schedule are not in this conversation, STOP and ask me for them. Set up the test: - Give me SQL that adds a few clearly marked test records: some the task should process, and some it must leave alone - Temporarily change the job to a short schedule, such as every two minutes, with cron.alter_job — or schedule a separate test job that I remove afterwards. Tell me which, and exactly how to undo it. Then walk me through these tests and tell me what to check for each: 1. Manual run: the underlying action still works when run by hand 2. Cron run: the job ran on the short schedule, and its run shows as succeeded 3. Right records: the test records that should be processed were processed 4. Untouched records: the records it must leave alone are unchanged 5. Empty run: with nothing left to process, the next run completes cleanly and reports zero 6. No duplicates: another run does not repeat completed work 7. Failure: a simulated failure — for example a test record the action cannot handle, or a temporary switch you remove afterwards — leaves that record unprocessed, and the others still complete 8. Outside service (Edge Function path): a failed or timed-out call to the outside service is logged, and the record is left for the next run 9. Secrets: no secret appears in frontend code, the repository, the job's command, the function's responses or its logs 10. Restore: the job is back on its final schedule, any test job is removed, and the test records are deleted For an Edge Function job, a succeeded Cron run only means the request was sent. Also check the HTTP response status and the function's logs. Use test data only. Do not change real records or send anything to real users. Report expected versus actual for every test, then finish with exactly one verdict: AUTOMATION READY or NEEDS ATTENTION If NEEDS ATTENTION, list only what is still failing.
Continue when the AI reports AUTOMATION READY and the job is back on its final schedule. If it reports NEEDS ATTENTION, fix the listed issues and run the test again.
Check Recent Runs
See when the job ran and whether each run succeeded — on the Cron page or with one query.
Integrations Cron Jobs
Select History next to your job to see when each run started, how long it took and whether it succeeded or failed. Or run this read-only query in the SQL Editor:
Recent Cron runs
select j.jobname, d.status, d.return_message, d.start_time, d.end_time from cron.job_run_details d join cron.job j on j.jobid = d.jobid order by d.start_time desc limit 20;
For an Edge Function job, a succeeded run only means Cron sent the request. Check the function's own result in its logs:
Edge Functions Your function Logs
Recent function responses
select id, status_code, timed_out, error_msg, created from net._http_response order by created desc limit 20;
A status_code of 200 means the function succeeded. These responses are kept for a few hours only, so check them soon after a run.
I can see my job's recent runs and whether each one succeeded
I know where to look when a run fails
Your web app now runs the task automatically on schedule, even when nobody has the app open.