diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts
index 16e9cd7d1f7f5..2b7d9e57a0f3b 100644
--- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts
+++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts
@@ -2551,6 +2551,32 @@ export const platform: NavMenuConstant = {
name: 'Credits',
url: '/guides/platform/credits' as `/${string}`,
},
+ {
+ name: 'AWS Marketplace',
+ url: '/guides/platform/aws-marketplace',
+ items: [
+ {
+ name: 'Getting Started',
+ url: '/guides/platform/aws-marketplace/getting-started',
+ },
+ {
+ name: 'Account Setup',
+ url: '/guides/platform/aws-marketplace/account-setup',
+ },
+ {
+ name: 'Manage your subscription',
+ url: '/guides/platform/aws-marketplace/manage-your-subscription',
+ },
+ {
+ name: 'Invoices',
+ url: '/guides/platform/aws-marketplace/invoices',
+ },
+ {
+ name: 'FAQ',
+ url: '/guides/platform/aws-marketplace/faq',
+ },
+ ],
+ },
{
name: 'Billing FAQ',
url: '/guides/platform/billing-faq' as `/${string}`,
diff --git a/apps/docs/content/guides/database/postgres/row-level-security.mdx b/apps/docs/content/guides/database/postgres/row-level-security.mdx
index b527370a33c63..2abe63dc067b6 100644
--- a/apps/docs/content/guides/database/postgres/row-level-security.mdx
+++ b/apps/docs/content/guides/database/postgres/row-level-security.mdx
@@ -59,6 +59,32 @@ alter table "table_name" enable row level security;
Once you have enabled RLS, no data will be accessible via the [API](/docs/guides/api) when using the public `anon` key, until you create policies.
+
+
+When a request is made without an authenticated user (e.g., no access token is provided or the session has expired), `auth.uid()` returns `null`.
+
+This means that a policy like:
+
+```sql
+USING (auth.uid() = user_id)
+```
+
+will silently fail for unauthenticated users, because:
+
+```sql
+null = user_id
+```
+
+is always false in SQL.
+
+To avoid confusion and make your intention clear, we recommend explicitly checking for authentication:
+
+```sql
+USING (auth.uid() IS NOT NULL AND auth.uid() = user_id)
+```
+
+
+
## Authenticated and unauthenticated roles
Supabase maps every request to one of the roles:
diff --git a/apps/docs/content/guides/deployment/branching.mdx b/apps/docs/content/guides/deployment/branching.mdx
index 6749078bc03f0..74782ceb45579 100644
--- a/apps/docs/content/guides/deployment/branching.mdx
+++ b/apps/docs/content/guides/deployment/branching.mdx
@@ -14,3 +14,17 @@ Supabase branches create separate environments that spin off from your main proj
- **Persistent Branches**: Persistent branches are long-lived branches. They aren't automatically paused or deleted due to non-inactivity or merging.
- **Managing Branches**: You can create, review, and merge branches either automatically via our [GitHub integration](/docs/guides/deployment/branching/github-integration) or directly [through the dashboard](/docs/guides/deployment/branching/dashboard) (currently in beta). All branches show up in the branches page in the dashboard, regardless of how they were created.
- **Data-less**: New branches do not start with any data from your main project. This is meant to better protect your sensitive production data. To start your branches with data, you can use a [seed file](/docs/guides/deployment/branching/github-integration#seeding) if using the GitHub integration.
+
+## Deploying to production
+
+When you merge any branch into your main project, Supabase automatically runs a deployment workflow to deploy your changes to production. The deployment workflow is expressed as a Directed Acyclic Graph where each node represents one of the following deployment steps.
+
+1. **Clone** - Checks out your repository at the specified git branch (optional for [Branching via Dashboard](/docs/guides/deployment/branching/dashboard))
+2. **Pull** - Retrieves database migrations from your main project (also initialises the migration history table when Branching via Dashboard)
+3. **Health** - Waits up to 2 minutes for all Supabase services on your branch to be running and healthy, including Auth, API, Database, Storage, and Realtime
+4. **Configure** - Updates service configurations based on your config.toml file (only available for [Branching via GitHub](/docs/guides/deployment/branching/github-integration))
+5. **Migrate** - Applies pending database migrations and vault secrets to your branch
+6. **Seed** - Runs seed files to populate your branch with initial data (must be [enabled in config.toml](/docs/guides/deployment/branching/configuration#branch-configuration-with-remotes) for persistent branches)
+7. **Deploy** - Deploys any changed Edge Functions and updates function secrets
+
+If a parent deployment step fails, all dependent children steps will be skipped. For e.g., if your database migrations failed at step 5, our runner will not seed your branch because step 6 is skipped. If you are using GitHub integration, the same deployment workflow will be run on every commit pushed to your git branch.
diff --git a/apps/docs/content/guides/deployment/branching/troubleshooting.mdx b/apps/docs/content/guides/deployment/branching/troubleshooting.mdx
index bc494b8b274cc..1ce577728f891 100644
--- a/apps/docs/content/guides/deployment/branching/troubleshooting.mdx
+++ b/apps/docs/content/guides/deployment/branching/troubleshooting.mdx
@@ -6,6 +6,19 @@ subtitle: 'Common issues and solutions for Supabase branching'
This guide covers common issues you might encounter when using Supabase branching and how to resolve them.
+## Monitoring deployments
+
+To check deployment status and troubleshoot failures:
+
+1. Go to your project dashboard
+2. Navigate to "Manage Branches"
+3. Click on your branch to view deployment logs
+4. Check the "View logs" section for detailed error messages
+
+For programmatic monitoring, you can use the [Management API](https://api.supabase.com/api/v1#tag/environments/post/v1/projects/{ref}/branches) to poll branch status.
+
+For detailed troubleshooting guidance, see our [Troubleshooting guide](/docs/guides/deployment/branching/troubleshooting).
+
## Common issues
### Rolling back migrations
diff --git a/apps/docs/content/guides/platform/aws-marketplace.mdx b/apps/docs/content/guides/platform/aws-marketplace.mdx
new file mode 100644
index 0000000000000..0d9c1b3ab7661
--- /dev/null
+++ b/apps/docs/content/guides/platform/aws-marketplace.mdx
@@ -0,0 +1,25 @@
+---
+id: 'aws-marketplace'
+title: 'AWS Marketplace'
+---
+
+You can purchase Supabase through the AWS Marketplace. Buying through AWS Marketplace can mean simpler billing, faster progress toward your AWS spend commitments, and centralized purchasing across all your AWS accounts. Start the purchase process from our marketplace [product page](https://aws.amazon.com/marketplace/pp/prodview-zjciuce2qsb3q).
+
+When you make a purchase on AWS Marketplace, AWS will calculate sales taxes, VAT, GST, service tax, etc. (“Indirect Taxes”), if applicable, based on the location of your AWS account. You can find more details in the [AWS tax help guide](https://aws.amazon.com/tax-help/marketplace-buyers/).
+
+### Plans available through the AWS Marketplace
+
+- Free Plan: not available
+- Pro Plan: available, self-serve
+- Team Plan: available, self-serve
+- Enterprise Plan: available, via AWS Marketplace Private Offer. [Contact us](https://forms.supabase.com/enterprise) for more information.
+
+## More information
+
+- Implications of managing your Supabase organization through the AWS Marketplace. Refer to the [Account Setup guide](./aws-marketplace/account-setup#implications-of-linking-a-supabase-organization-to-a-marketplace-subscription).
+- [AWS Marketplace FAQ](./aws-marketplace/faq)
+- General guidance on using the AWS Marketplace as a buyer. Refer to the [AWS documentation](https://docs.aws.amazon.com/marketplace/latest/buyerguide/using-aws-marketplace-as-a-subscriber.html).
+
+## Next steps
+
+- Purchase Supabase through the AWS Marketplace. Refer to the [Getting Started guide](./aws-marketplace/getting-started).
diff --git a/apps/docs/content/guides/platform/aws-marketplace/account-setup.mdx b/apps/docs/content/guides/platform/aws-marketplace/account-setup.mdx
new file mode 100644
index 0000000000000..2eea2f468569d
--- /dev/null
+++ b/apps/docs/content/guides/platform/aws-marketplace/account-setup.mdx
@@ -0,0 +1,39 @@
+---
+id: 'account-setup'
+title: 'Account Setup'
+---
+
+After purchasing a Supabase subscription on the AWS Marketplace, the next and final step is to link the newly purchased subscription to a Supabase organization. This can either be an existing organization or a newly created one.
+
+An AWS Marketplace subscription is linked to exactly one Supabase organization. If you want to manage multiple organizations through the AWS Marketplace, you must purchase a separate marketplace subscription for each organization.
+
+
+
+## Implications of linking a Supabase organization to a marketplace subscription
+
+- The billing details from your AWS account, such as the billing address and tax ID, are used. These details are managed through the [AWS Billing and Cost Management console](https://console.aws.amazon.com/billing).
+- The subscription plan is managed through the AWS Marketplace. You can read more about this in the [Manage your subscription](./manage-your-subscription#manage-your-subscription-plan) guide.
+- Charges will come from AWS rather than Supabase, using the default payment method set in your AWS account.
+- The [Spend Cap](/docs/guides/platform/cost-control#spend-cap) for the organization is disabled. The Spend Cap is not available for organizations managed through AWS.
+- When you downgrade your plan to the Free Plan, all projects within the organization will be paused if you exceed the [free projects limit](/docs/guides/platform/billing-on-supabase#free-plan).
+
+### Linking an existing Supabase organization
+
+Linking an existing organization will result in the following:
+
+- The organization will be upgraded or downgraded to the plan purchased on the AWS Marketplace.
+- The organization’s billing cycle will be adjusted. The start date will be set to the date your marketplace subscription became active.
+- The credit card you have on file with Supabase may receive a closing charge. This charge covers usage costs incurred up until the point when the marketplace subscription became active.
+
+## Prerequisites for linking a Supabase organization to a marketplace subscription
+
+- The Supabase user must have the Owner or Admin role
+- There must be no overdue invoices within the organization
+- The organization must not already be managed through another marketplace (e.g. Vercel Marketplace)
diff --git a/apps/docs/content/guides/platform/aws-marketplace/faq.mdx b/apps/docs/content/guides/platform/aws-marketplace/faq.mdx
new file mode 100644
index 0000000000000..14e25ae8c49fb
--- /dev/null
+++ b/apps/docs/content/guides/platform/aws-marketplace/faq.mdx
@@ -0,0 +1,21 @@
+---
+id: 'aws-marketplace-faq'
+title: 'AWS Marketplace FAQ'
+---
+
+#### The payment for completing the subscription on the AWS Marketplace fails.
+
+For more information on payment errors, refer to the [AWS documentation](https://docs.aws.amazon.com/marketplace/latest/buyerguide/buyer-paying-for-products.html#payment-methods).
+
+#### How can the Spend Cap for an organization managed through the AWS Marketplace be enabled?
+
+For organizations on the Pro Plan that are managed through the AWS Marketplace, the Spend Cap is not available.
+In your AWS account, you can set up a budget for marketplace purchases (or for a specific marketplace product) and receive notifications once the budget is exceeded.
+
+#### How to cancel your AWS Marketplace subscription
+
+You can cancel your marketplace subscription within 48 hours of purchase. To do so, open a support ticket via the Supabase dashboard. After the 48-hour period, cancellation is no longer possible. If you cancel within the first 48 hours, the upfront charge for the fixed subscription fee will be refunded. Any usage costs incurred up to that point will not be refunded.
+
+#### Does purchasing Supabase through the AWS Marketplace count toward your AWS spend commitment?
+
+Yes, marketplace purchases do count toward the spend commitment.
diff --git a/apps/docs/content/guides/platform/aws-marketplace/getting-started.mdx b/apps/docs/content/guides/platform/aws-marketplace/getting-started.mdx
new file mode 100644
index 0000000000000..6db2910d231ec
--- /dev/null
+++ b/apps/docs/content/guides/platform/aws-marketplace/getting-started.mdx
@@ -0,0 +1,88 @@
+---
+id: 'getting-started'
+title: 'Getting Started'
+---
+
+## Before you start
+
+Depending on whether a Supabase organization is managed and billed through the AWS Marketplace or directly through the Supabase platform, there are differences. To help you make an informed decision about which approach is better suited for your needs, you can find an overview of these differences in the table below.
+
+| Feature/Aspect | Managed via AWS Marketplace | Managed directly via Supabase platform |
+| -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| Available Plans | Pro, Team, Enterprise | Free, Pro, Team, Enterprise |
+| Mid-cycle downgrades | No | Yes |
+| Cost Control | Spend Cap not available | Spend Cap available |
+| Downgrade Behaviour | If a downgrade to the Free Plan causes you to exceed the [free projects limit](/docs/guides/platform/billing-on-supabase#free-plan), all projects will be paused. | If a downgrade to the Free Plan causes you to exceed the [free projects limit](/docs/guides/platform/billing-on-supabase#free-plan), you have the option to prevent pausing by transferring projects. |
+| Invoicing | Separate invoices, one for fixed costs and one for usage costs | One invoice for both fixed costs and usage costs |
+
+## Purchase Supabase through the AWS Marketplace
+
+Purchasing Supabase through the AWS Marketplace involves two steps. First, you purchase the corresponding subscription on the marketplace. Then, to complete the setup, you must link this subscription to a Supabase organization on the Supabase platform.
+
+For more details on completing the setup and what it means to link an organization, see our [Account Setup guide](./account-setup).
+
+
+
+
+ Go to the [Supabase product page on the AWS Marketplace](https://aws.amazon.com/marketplace/pp/prodview-zjciuce2qsb3q) and click "View purchase options".
+
+
+
+
+
+ Select the desired plan (Pro Plan or Team Plan) and configure whether the subscription should automatically renew after one month.
+
+
+
+ Disabling auto-renewal means that the subscription will be downgraded to the Free Plan after one month.
+
+ If the downgrade causes you to exceed the [free projects limit](/docs/guides/platform/billing-on-supabase#free-plan), **all** projects within the organization will be paused. We do not make the decision about which projects continue to run and which are paused. You must then decide which projects you want to keep active and manually reactivate them through the Supabase dashboard.
+
+
+
+
+
+
+
+
+ Click "Subscribe" at the bottom of the page.
+
+
+
+
+
+ After the payment has been confirmed and your marketplace subscription is active, click "Set up your account" to be redirected to the Supabase platform.
+
+
+
+
+
+ Complete the setup by linking a Supabase organization to the AWS Marketplace subscription.
+
+
+
+
+
diff --git a/apps/docs/content/guides/platform/aws-marketplace/invoices.mdx b/apps/docs/content/guides/platform/aws-marketplace/invoices.mdx
new file mode 100644
index 0000000000000..85c9d5a453373
--- /dev/null
+++ b/apps/docs/content/guides/platform/aws-marketplace/invoices.mdx
@@ -0,0 +1,32 @@
+---
+id: 'invoices'
+title: 'Invoices'
+---
+
+## Where to find your invoices
+
+You can view your invoices in the [AWS Billing and Cost Management console](https://console.aws.amazon.com/billing/home#/bills) under the "Bills" section.
+
+
+
+## What invoices you get from AWS
+
+You'll receive two invoices for your marketplace subscription.
+
+### Invoice 1 - charge type "subscription"
+
+- What for: The fixed subscription fee paid in advance
+- When: At the time of subscription, and in subsequent months on the same day of the month the subscription was started
+
+### Invoice 2 - charge type "usage"
+
+- What for: Usage that exceeds the quota included in the plan, or usage not covered by the plan (e.g. Custom Domain add-on, IPv4 add-on, additionally provisioned Disk IOPS).
+- When: No later than the third day of the month for the previous month. This is independent of your subscription’s billing cycle and instead covers the period from the first to the last day of the previous month.
+
+## More information
+
+- Detailed explanations of how each usage item is billed, independent of the AWS Marketplace. Refer to the [Manage Your Usage guide](../manage-your-usage).
diff --git a/apps/docs/content/guides/platform/aws-marketplace/manage-your-subscription.mdx b/apps/docs/content/guides/platform/aws-marketplace/manage-your-subscription.mdx
new file mode 100644
index 0000000000000..434b89ee77308
--- /dev/null
+++ b/apps/docs/content/guides/platform/aws-marketplace/manage-your-subscription.mdx
@@ -0,0 +1,63 @@
+---
+id: 'manage-your-subscription'
+title: 'Manage your subscription'
+---
+
+## Manage your subscription plan
+
+Plan changes are not made on the Supabase dashboard, but instead through the AWS Marketplace. The easiest way to navigate to the corresponding page on the marketplace is through the Supabase dashboard.
+
+1. On the [organization's billing page](/dashboard/org/_/billing), go to section **Subscription Plan**
+2. Click **Change subscription plan**
+3. On the side panel, follow the link to the AWS Marketplace
+
+### Upgrade
+
+You can upgrade your plan at any time. The new plan will be active immediately, and you will be charged a prorated amount for the remainder of the current billing cycle. The charge for the upgrade also factors in the upfront payment you have already made for your existing plan.
+
+
+
+### Downgrade
+
+Downgrades are only possible at the end of the billing cycle, not in the middle of a billing cycle.
+
+#### Downgrade to the Free Plan
+
+If you want your subscription to be downgraded to the Free Plan at the end of the current billing cycle, you need to disable auto-renewal for the marketplace subscription.
+
+
+
+If the downgrade causes you to exceed the [free projects limit](/docs/guides/platform/billing-on-supabase#free-plan), **all** projects within the organization will be paused. We do not make the decision about which projects continue to run and which are paused. You must then decide which projects you want to keep active and manually reactivate them through the Supabase dashboard.
+
+
+
+
+
+#### Downgrade to a paid plan
+
+A downgrade to a paid plan (Pro Plan / Team Plan) involves two steps.
+
+**Step 1:** Let the current subscription on the higher plan expire, meaning turn off auto-renewal
+**Step 2:** Start a new subscription on the lower plan
+
+## Manage your payment methods
+
+You can manage your payment methods through the [AWS Billing and Cost Management console](https://console.aws.amazon.com/billing).
+
+## Manage your billing details
+
+You can manage billing details, such as the billing address or tax ID, through the [AWS Billing and Cost Management console](https://console.aws.amazon.com/billing).
diff --git a/apps/docs/content/guides/platform/custom-domains.mdx b/apps/docs/content/guides/platform/custom-domains.mdx
index d4899e9e040f8..9ef5c8ec927bc 100644
--- a/apps/docs/content/guides/platform/custom-domains.mdx
+++ b/apps/docs/content/guides/platform/custom-domains.mdx
@@ -5,7 +5,7 @@ description: 'Use a custom domain instead of the default Supabase domain for you
tocVideo: '6rcGnW_Mh-0'
---
-Custom domains allow you to present a branded experience to your users. These are available as an [add-on for projects on a paid plan](/dashboard/project/_/settings/addons?panel=customDomain).
+Custom domains allow you to present a branded experience to your users. These are available as a [paid add-on for projects on a paid plan](/dashboard/project/_/settings/addons?panel=customDomain).
There are two types of domains supported by Supabase:
@@ -24,6 +24,12 @@ Custom domains change the way your project's URLs appear to your users. This is
Custom domains help you keep your APIs portable for the long term. By using a custom domain you can migrate from one Supabase project to another, or make it easier to version APIs in the future.
+### Limitations
+
+- Custom domains are not intended to enable hosting of frontend applications through [Edge Functions](/docs/guides/functions).
+- You can only attach a single custom domain to any given Supabase project. It is not possible to break out your project's resources into multiple custom domains.
+- Custom domains can only be powered by CNAME records.
+
### Configure a custom domain using the Supabase dashboard
Follow the **Custom Domains** steps in the [General Settings](/dashboard/project/_/settings/general) page in the Dashboard to set up a custom domain for your project.
diff --git a/apps/docs/content/guides/realtime.mdx b/apps/docs/content/guides/realtime.mdx
index 8333973ae784a..9a4331929cb1f 100644
--- a/apps/docs/content/guides/realtime.mdx
+++ b/apps/docs/content/guides/realtime.mdx
@@ -20,7 +20,7 @@ Supabase provides a globally distributed [Realtime](https://github.com/supabase/
- **Multiplayer games** - Synchronized game state and player interactions
- **Social features** - Live notifications, reactions, and user activity feeds
-Check the [Getting Started](/docs/guides/realtime/getting-started) guide to get started.
+Check the [Getting Started](/docs/guides/realtime/getting_started) guide to get started.
## Examples
diff --git a/apps/docs/public/img/guides/platform/aws-marketplace-change-plan.png b/apps/docs/public/img/guides/platform/aws-marketplace-change-plan.png
new file mode 100644
index 0000000000000..fcad1c5e0b2f3
Binary files /dev/null and b/apps/docs/public/img/guides/platform/aws-marketplace-change-plan.png differ
diff --git a/apps/docs/public/img/guides/platform/aws-marketplace-configure-auto-renewal.png b/apps/docs/public/img/guides/platform/aws-marketplace-configure-auto-renewal.png
new file mode 100644
index 0000000000000..19c3fff6a5936
Binary files /dev/null and b/apps/docs/public/img/guides/platform/aws-marketplace-configure-auto-renewal.png differ
diff --git a/apps/docs/public/img/guides/platform/aws-marketplace-invoices.png b/apps/docs/public/img/guides/platform/aws-marketplace-invoices.png
new file mode 100644
index 0000000000000..1d00e73383c6e
Binary files /dev/null and b/apps/docs/public/img/guides/platform/aws-marketplace-invoices.png differ
diff --git a/apps/docs/public/img/guides/platform/aws-marketplace-listing-overview.png b/apps/docs/public/img/guides/platform/aws-marketplace-listing-overview.png
new file mode 100644
index 0000000000000..18f4c4a8900dc
Binary files /dev/null and b/apps/docs/public/img/guides/platform/aws-marketplace-listing-overview.png differ
diff --git a/apps/docs/public/img/guides/platform/aws-marketplace-listing-purchase-options.png b/apps/docs/public/img/guides/platform/aws-marketplace-listing-purchase-options.png
new file mode 100644
index 0000000000000..54801e5f988c1
Binary files /dev/null and b/apps/docs/public/img/guides/platform/aws-marketplace-listing-purchase-options.png differ
diff --git a/apps/docs/public/img/guides/platform/aws-marketplace-listing-subscribe.png b/apps/docs/public/img/guides/platform/aws-marketplace-listing-subscribe.png
new file mode 100644
index 0000000000000..18a574c49f475
Binary files /dev/null and b/apps/docs/public/img/guides/platform/aws-marketplace-listing-subscribe.png differ
diff --git a/apps/docs/public/img/guides/platform/aws-marketplace-listing-success.png b/apps/docs/public/img/guides/platform/aws-marketplace-listing-success.png
new file mode 100644
index 0000000000000..ffd9ff83ff001
Binary files /dev/null and b/apps/docs/public/img/guides/platform/aws-marketplace-listing-success.png differ
diff --git a/apps/docs/public/img/guides/platform/aws-marketplace-onboarding-page--dark.png b/apps/docs/public/img/guides/platform/aws-marketplace-onboarding-page--dark.png
new file mode 100644
index 0000000000000..da1d3a20a2cb8
Binary files /dev/null and b/apps/docs/public/img/guides/platform/aws-marketplace-onboarding-page--dark.png differ
diff --git a/apps/docs/public/img/guides/platform/aws-marketplace-onboarding-page--light.png b/apps/docs/public/img/guides/platform/aws-marketplace-onboarding-page--light.png
new file mode 100644
index 0000000000000..9b78e4f44af72
Binary files /dev/null and b/apps/docs/public/img/guides/platform/aws-marketplace-onboarding-page--light.png differ
diff --git a/apps/docs/public/img/guides/platform/aws-marketplace-onboarding-page-extended--dark.png b/apps/docs/public/img/guides/platform/aws-marketplace-onboarding-page-extended--dark.png
new file mode 100644
index 0000000000000..1ee94172cbbdd
Binary files /dev/null and b/apps/docs/public/img/guides/platform/aws-marketplace-onboarding-page-extended--dark.png differ
diff --git a/apps/docs/public/img/guides/platform/aws-marketplace-onboarding-page-extended--light.png b/apps/docs/public/img/guides/platform/aws-marketplace-onboarding-page-extended--light.png
new file mode 100644
index 0000000000000..8d351159b6d72
Binary files /dev/null and b/apps/docs/public/img/guides/platform/aws-marketplace-onboarding-page-extended--light.png differ
diff --git a/apps/studio/components/interfaces/Billing/NoProjectsOnPaidOrgInfo.tsx b/apps/studio/components/interfaces/Billing/NoProjectsOnPaidOrgInfo.tsx
index 84f685f70bfb2..372b8257b9e2f 100644
--- a/apps/studio/components/interfaces/Billing/NoProjectsOnPaidOrgInfo.tsx
+++ b/apps/studio/components/interfaces/Billing/NoProjectsOnPaidOrgInfo.tsx
@@ -1,4 +1,4 @@
-import { useProjectsQuery } from 'data/projects/projects-query'
+import { useOrgProjectsInfiniteQuery } from 'data/projects/org-projects-infinite-query'
import Link from 'next/link'
import type { Organization } from 'types'
import { Admonition } from 'ui-patterns'
@@ -8,10 +8,8 @@ interface NoProjectsOnPaidOrgInfoProps {
}
export const NoProjectsOnPaidOrgInfo = ({ organization }: NoProjectsOnPaidOrgInfoProps) => {
- const { data } = useProjectsQuery({})
- const projectCount =
- (data?.projects ?? []).filter((project) => project.organization_id === organization?.id)
- .length ?? 0
+ const { data } = useOrgProjectsInfiniteQuery({ slug: organization?.slug })
+ const projectCount = data?.pages[0].pagination.count ?? 0
if (
projectCount > 0 ||
diff --git a/apps/studio/components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionDetails.constants.ts b/apps/studio/components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionDetails.constants.ts
index 505a373fa6f9c..f8492a1c6947d 100644
--- a/apps/studio/components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionDetails.constants.ts
+++ b/apps/studio/components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionDetails.constants.ts
@@ -3,7 +3,12 @@ interface InvocationTab {
label: string
language: 'bash' | 'js' | 'ts' | 'dart' | 'python'
hideLineNumbers?: boolean
- code: (functionUrl: string, functionName: string, apiKey: string) => string
+ code: (props: {
+ showKey: boolean
+ functionUrl: string
+ functionName: string
+ apiKey: string
+ }) => string
}
export const INVOCATION_TABS: InvocationTab[] = [
@@ -11,17 +16,24 @@ export const INVOCATION_TABS: InvocationTab[] = [
id: 'curl',
label: 'cURL',
language: 'bash',
- code: (functionUrl, _, apiKey) => `curl -L -X POST '${functionUrl}' \\
- -H 'Authorization: Bearer ${apiKey}' \\${apiKey.includes('publishable') ? `\n -H 'apikey: ${apiKey}' \\` : ''}
+ code: ({ showKey, functionUrl, apiKey }) => {
+ const obfuscatedName = apiKey.includes('publishable')
+ ? 'SUPABASE_PUBLISHABLE_DEFAULT_KEY'
+ : 'SUPABASE_ANON_KEY'
+ const keyValue = showKey ? apiKey : obfuscatedName
+
+ return `curl -L -X POST '${functionUrl}' \\
+ -H 'Authorization: Bearer ${keyValue}' \\${apiKey.includes('publishable') ? `\n -H 'apikey: ${keyValue}' \\` : ''}
-H 'Content-Type: application/json' \\
- --data '{"name":"Functions"}'`,
+ --data '{"name":"Functions"}'`
+ },
},
{
id: 'supabase-js',
label: 'JavaScript',
language: 'js',
hideLineNumbers: true,
- code: (_, functionName) => `import { createClient } from '@supabase/supabase-js'
+ code: ({ functionName }) => `import { createClient } from '@supabase/supabase-js'
const supabase = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_ANON_KEY)
const { data, error } = await supabase.functions.invoke('${functionName}', {
body: { name: 'Functions' },
@@ -32,7 +44,7 @@ const { data, error } = await supabase.functions.invoke('${functionName}', {
label: 'Swift',
language: 'ts',
hideLineNumbers: true,
- code: (_, functionName) => `struct Response: Decodable {
+ code: ({ functionName }) => `struct Response: Decodable {
// Expected response definition
}
@@ -49,10 +61,9 @@ let response: Response = try await supabase.functions
label: 'Flutter',
language: 'dart',
hideLineNumbers: true,
- code: (
- _,
- functionName
- ) => `final res = await supabase.functions.invoke('${functionName}', body: {'name': 'Functions'});
+ code: ({
+ functionName,
+ }) => `final res = await supabase.functions.invoke('${functionName}', body: {'name': 'Functions'});
final data = res.data;`,
},
{
@@ -60,7 +71,7 @@ final data = res.data;`,
label: 'Python',
language: 'python',
hideLineNumbers: true,
- code: (_, functionName) => `response = supabase.functions.invoke(
+ code: ({ functionName }) => `response = supabase.functions.invoke(
"${functionName}",
invoke_options={"body": {"name": "Functions"}}
)`,
diff --git a/apps/studio/components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionDetails.tsx b/apps/studio/components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionDetails.tsx
index 2d3f39195b068..f3d6ebe64387b 100644
--- a/apps/studio/components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionDetails.tsx
+++ b/apps/studio/components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionDetails.tsx
@@ -33,6 +33,7 @@ import {
CardTitle,
cn,
CodeBlock,
+ copyToClipboard,
CriticalIcon,
Form_Shadcn_,
FormControl_Shadcn_,
@@ -60,11 +61,6 @@ const FormSchema = z.object({
export const EdgeFunctionDetails = () => {
const router = useRouter()
const { ref: projectRef, functionSlug } = useParams()
- const [showDeleteModal, setShowDeleteModal] = useState(false)
- const { can: canUpdateEdgeFunction } = useAsyncCheckPermissions(
- PermissionAction.FUNCTIONS_WRITE,
- '*'
- )
const showAllEdgeFunctionInvocationExamples = useIsFeatureEnabled(
'edge_functions:show_all_edge_function_invocation_examples'
@@ -74,6 +70,15 @@ export const EdgeFunctionDetails = () => {
return INVOCATION_TABS.filter((tab) => tab.id === 'curl' || tab.id === 'supabase-js')
}, [showAllEdgeFunctionInvocationExamples])
+ const [showKey, setShowKey] = useState(false)
+ const [selectedTab, setSelectedTab] = useState(invocationTabs[0].id)
+ const [showDeleteModal, setShowDeleteModal] = useState(false)
+
+ const { can: canUpdateEdgeFunction } = useAsyncCheckPermissions(
+ PermissionAction.FUNCTIONS_WRITE,
+ '*'
+ )
+
const { data: apiKeys } = useAPIKeysQuery({ projectRef })
const { data: settings } = useProjectSettingsV2Query({ projectRef })
const { data: customDomainData } = useCustomDomainsQuery({ projectRef })
@@ -231,34 +236,69 @@ export const EdgeFunctionDetails = () => {
+
Invoke function
-
-
-
+
+
+
{invocationTabs.map((tab) => (
{tab.label}
))}
+ {selectedTab === 'curl' && (
+
+ )}
- {invocationTabs.map((tab) => (
-
-
)}
diff --git a/apps/studio/components/interfaces/Realtime/Inspector/ChooseChannelPopover/index.tsx b/apps/studio/components/interfaces/Realtime/Inspector/ChooseChannelPopover/index.tsx
index 3d16108accc52..f106a20a12054 100644
--- a/apps/studio/components/interfaces/Realtime/Inspector/ChooseChannelPopover/index.tsx
+++ b/apps/studio/components/interfaces/Realtime/Inspector/ChooseChannelPopover/index.tsx
@@ -1,5 +1,5 @@
import { zodResolver } from '@hookform/resolvers/zod'
-import { useParams } from 'common'
+import { IS_PLATFORM, useParams } from 'common'
import { ChevronDown } from 'lucide-react'
import { Dispatch, SetStateAction, useState } from 'react'
import { useForm } from 'react-hook-form'
@@ -67,7 +67,7 @@ export const ChooseChannelPopover = ({ config, onChangeConfig }: ChooseChannelPo
let token = config.token
// [Joshen] Refresh if starting to listen + using temp API key, since it has a low refresh rate
- if (token.startsWith('sb_temp')) {
+ if (token.startsWith('sb_temp') || !IS_PLATFORM) {
const data = await getTemporaryAPIKey({ projectRef: config.projectRef, expiry: 3600 })
token = data.api_key
}
diff --git a/apps/studio/components/interfaces/Realtime/Inspector/Header.tsx b/apps/studio/components/interfaces/Realtime/Inspector/Header.tsx
index 10d9573184dd1..e37f6039714fa 100644
--- a/apps/studio/components/interfaces/Realtime/Inspector/Header.tsx
+++ b/apps/studio/components/interfaces/Realtime/Inspector/Header.tsx
@@ -2,7 +2,7 @@ import { PermissionAction } from '@supabase/shared-types/out/constants'
import { PlayCircle, StopCircle } from 'lucide-react'
import { Dispatch, SetStateAction } from 'react'
-import { useParams } from 'common'
+import { IS_PLATFORM, useParams } from 'common'
import { ButtonTooltip } from 'components/ui/ButtonTooltip'
import { getTemporaryAPIKey } from 'data/api-keys/temp-api-keys-query'
import { useSendEventMutation } from 'data/telemetry/send-event-mutation'
@@ -41,7 +41,7 @@ export const Header = ({ config, onChangeConfig }: HeaderProps) => {
icon={config.enabled ? : }
onClick={async () => {
// [Joshen] Refresh if starting to listen + using temp API key, since it has a low refresh rate
- if (!config.enabled && config.token.startsWith('sb_temp')) {
+ if (!config.enabled && (config.token.startsWith('sb_temp') || !IS_PLATFORM)) {
const data = await getTemporaryAPIKey({ projectRef: config.projectRef, expiry: 3600 })
const token = data.api_key
onChangeConfig({ ...config, token, enabled: !config.enabled })
diff --git a/apps/studio/components/interfaces/Reports/Reports.utils.tsx b/apps/studio/components/interfaces/Reports/Reports.utils.tsx
index f0eef8b93adaf..0be4bb7310739 100644
--- a/apps/studio/components/interfaces/Reports/Reports.utils.tsx
+++ b/apps/studio/components/interfaces/Reports/Reports.utils.tsx
@@ -3,6 +3,12 @@ import dayjs from 'dayjs'
import useDbQuery, { DbQueryHook } from 'hooks/analytics/useDbQuery'
import useLogsQuery, { LogsQueryHook } from 'hooks/analytics/useLogsQuery'
import type { BaseQueries, PresetConfig, ReportQuery } from './Reports.types'
+import {
+ isUnixMicro,
+ unixMicroToIsoTimestamp,
+} from 'components/interfaces/Settings/Logs/Logs.utils'
+import { REPORT_STATUS_CODE_COLORS } from 'data/reports/report.utils'
+import { getHttpStatusCodeInfo } from 'lib/http-status-codes'
/**
* Converts a query params string to an object
@@ -73,3 +79,108 @@ export const formatTimestamp = (
return 'Invalid Date'
}
}
+
+/**
+ * Extracts distinct status codes from log data rows
+ */
+export function extractStatusCodesFromData(data: any[]): string[] {
+ const statusCodes = new Set()
+
+ data.forEach((item: any) => {
+ if (item.status_code !== undefined && item.status_code !== null) {
+ statusCodes.add(String(item.status_code))
+ }
+ })
+
+ return Array.from(statusCodes).sort()
+}
+
+/**
+ * Generates chart attributes for status codes with labels and colors
+ */
+export function generateStatusCodeAttributes(statusCodes: string[]) {
+ return statusCodes.map((code) => ({
+ attribute: code,
+ label: `${code} ${getHttpStatusCodeInfo(parseInt(code, 10)).label}`,
+ color: REPORT_STATUS_CODE_COLORS[code] || REPORT_STATUS_CODE_COLORS.default,
+ }))
+}
+
+/**
+ * Pivots rows of { timestamp, status_code, count } into { timestamp, [status_code]: count }
+ * and normalizes timestamps to ISO strings (UTC), filling missing codes with 0 per timestamp
+ */
+export function transformStatusCodeData(data: any[], statusCodes: string[]) {
+ const pivotedData = data.reduce((acc: Record, d: any) => {
+ const timestamp = isUnixMicro(d.timestamp)
+ ? unixMicroToIsoTimestamp(d.timestamp)
+ : dayjs.utc(d.timestamp).toISOString()
+ if (!acc[timestamp]) {
+ acc[timestamp] = { timestamp }
+ statusCodes.forEach((code) => {
+ acc[timestamp][code] = 0
+ })
+ }
+ const codeKey = String(d.status_code)
+ if (codeKey in acc[timestamp]) {
+ acc[timestamp][codeKey] = d.count
+ }
+ return acc
+ }, {})
+
+ return Object.values(pivotedData)
+}
+
+/**
+ * Extract distinct string values for a given field from data rows
+ */
+export function extractDistinctValuesFromData(data: any[], field: string): string[] {
+ const values = new Set()
+ data.forEach((item: any) => {
+ if (item[field] !== undefined && item[field] !== null) {
+ values.add(String(item[field]))
+ }
+ })
+ return Array.from(values).sort()
+}
+
+/**
+ * Generates chart attributes from a list of category values
+ */
+export function generateCategoryAttributes(
+ values: string[],
+ labelResolver?: (v: string) => string
+) {
+ return values.map((v) => ({
+ attribute: v,
+ label: labelResolver ? labelResolver(v) : v,
+ }))
+}
+
+/**
+ * Pivot rows of { timestamp, [categoryField], count } into { timestamp, [category]: count }
+ */
+export function transformCategoricalCountData(
+ data: any[],
+ categoryField: string,
+ categories: string[]
+) {
+ const pivotedData = data.reduce((acc: Record, d: any) => {
+ const timestamp = isUnixMicro(d.timestamp)
+ ? unixMicroToIsoTimestamp(d.timestamp)
+ : dayjs.utc(d.timestamp).toISOString()
+ if (!acc[timestamp]) {
+ acc[timestamp] = { timestamp }
+ categories.forEach((c) => {
+ acc[timestamp][c] = 0
+ })
+ }
+ const key = String(d[categoryField])
+ if (key in acc[timestamp]) {
+ acc[timestamp][key] = d.count
+ }
+ return acc
+ }, {})
+
+ return Object.values(pivotedData)
+}
diff --git a/apps/studio/components/interfaces/Reports/v2/ReportChartV2.tsx b/apps/studio/components/interfaces/Reports/v2/ReportChartV2.tsx
index 57c3c07f8336f..01f9ed5f0040b 100644
--- a/apps/studio/components/interfaces/Reports/v2/ReportChartV2.tsx
+++ b/apps/studio/components/interfaces/Reports/v2/ReportChartV2.tsx
@@ -117,6 +117,7 @@ export const ReportChartV2 = ({
) : (
{
+ const [copiedLink, setCopiedLink] = useState(null)
+
+ const copyLinkToClipboard = async () => {
+ // [jordi] We want to keep the existing query params (filters)
+ // But if the user has an anchor in the URL,
+ // we remove it and add the one for this section
+ // This is so the shared URL shows the exact same report as the one the user is on
+ const url = `${window.location.href.split('#')[0]}#${id}`
+ await copyToClipboard(url)
+ setCopiedLink(id)
+ setTimeout(() => setCopiedLink(null), 2000)
+ }
+
+ return (
+