diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml
deleted file mode 100644
index 0c9a3c46ebd..00000000000
--- a/.github/workflows/integration-tests.yml
+++ /dev/null
@@ -1,92 +0,0 @@
-name: Tests
-
-# Controls when the workflow will run
-on:
- schedule:
- - cron: '0 4/6 * * *'
- workflow_dispatch:
-
-# A workflow run is made up of one or more jobs that can run sequentially or in parallel
-jobs:
- autotests:
- name: Run tests and generate Allure Report
- strategy:
- matrix:
- node: ['14']
-
- runs-on: ubuntu-latest
- defaults:
- run:
- working-directory: ./tests
-
- steps:
- - uses: actions/checkout@v2
-
- - name: Set up JDK
- uses: actions/setup-java@v1
- with:
- java-version: 1.8
-
- - name: Set up Node
- uses: actions/setup-node@v1
- with:
- node-version: ${{ matrix.node }}
-
- - name: Install dependencies
- run: npm ci
-
- - uses: supabase/setup-cli@v1
- - run: supabase start
-
- - name: Run Test
- run: npm run test:local
- env:
- SUPABASE_DB_PORT: 54322
- SUPABASE_DB_PASS: postgres
- SUPABASE_DB_HOST: localhost
- SUPABASE_GOTRUE: http://localhost:54321
- SUPABASE_URL: http://localhost:54321
- SUPABASE_KEY_ANON: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24ifQ.625_WdcF3KHqz5amU0x2X5WWHP-OEs_4qj0ssLNHzTs
- SUPABASE_KEY_ADMIN: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6InNlcnZpY2Vfcm9sZSJ9.vI9obAHOGyVVKa3pD--kJlyxp-Z2zV9UUMAhKpNLAcU
-
- - name: Stop infrastructure
- if: always()
- run: supabase stop
-
- - name: Get Allure history
- uses: actions/checkout@v2
- if: always()
- continue-on-error: true
- with:
- repository: supabase/test-reports
- ref: gh-pages
- path: gh-pages
-
- - name: Allure Report action
- uses: simple-elf/allure-report-action@master
- if: always()
- with:
- github_repo: test-reports
- allure_results: tests/allure-results
- allure_history: allure-history
- subfolder: supabase-integration
- keep_reports: 50
-
- - name: Deploy report to Github Pages
- if: always()
- uses: peaceiris/actions-gh-pages@v2
- env:
- EXTERNAL_REPOSITORY: supabase/test-reports
- ACTIONS_DEPLOY_KEY: ${{ secrets.DEPLOY_TO_TEST_REPORTS_KEY }}
- PUBLISH_BRANCH: gh-pages
- PUBLISH_DIR: allure-history
-
- - name: Post the link to the report
- if: always()
- uses: Sibz/github-status-action@v1
- with:
- authToken: ${{ secrets.GITHUB_TOKEN }}
- context: 'Test report'
- state: 'success'
- sha: ${{ github.event.pull_request.head.sha || github.sha }}
- target_url: https://supabase.github.io/test-reports/supabase-integration/${{ github.run_number }}
diff --git a/.github/workflows/og_images.yml b/.github/workflows/og_images.yml
new file mode 100644
index 00000000000..2e1461461d2
--- /dev/null
+++ b/.github/workflows/og_images.yml
@@ -0,0 +1,28 @@
+name: Deploy OG Images
+
+on:
+ push:
+ branches:
+ - main
+ paths:
+ - 'supabase/functions/og-images'
+ workflow_dispatch:
+
+jobs:
+ deploy:
+ runs-on: ubuntu-latest
+
+ env:
+ SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }}
+ PROJECT_ID: ${{ secrets.PROJECT_ID }}
+
+ steps:
+ - name: Check out repo
+ - uses: actions/checkout@v3
+
+ - name: Setup the Supabase CLI
+ - uses: supabase/setup-cli@v1
+ with:
+ version: 1.0.0
+
+ - run: supabase functions deploy og-images --project-ref $PROJECT_ID
diff --git a/.github/workflows/prettier.yml b/.github/workflows/prettier.yml
index 43305fc1aed..462e45092a9 100644
--- a/.github/workflows/prettier.yml
+++ b/.github/workflows/prettier.yml
@@ -11,14 +11,17 @@ jobs:
steps:
- name: Check out repo
uses: actions/checkout@v2
- # disable this check while we migrate to monorepo
- name: Run prettier
- uses: EPMatt/reviewdog-action-prettier@v1
- with:
- github_token: ${{ secrets.github_token }}
- reporter: github-pr-review
- level: warning
- command: npx prettier -c 'apps/**/*.{js,jsx,ts,tsx,css,md,json}'
+ run: |-
+ npx prettier -c 'apps/**/*.{js,jsx,ts,tsx,css,md,json}'
+ # [Joshen] Temp disable the following due to an issue
+ # https://github.com/EPMatt/reviewdog-action-prettier/issues/34
+ # uses: EPMatt/reviewdog-action-prettier@v1
+ # with:
+ # github_token: ${{ secrets.github_token }}
+ # reporter: github-pr-review
+ # level: warning
+ # command: npx prettier -c 'apps/**/*.{js,jsx,ts,tsx,css,md,json}'
# run: |-
# function run_check() {
@@ -40,9 +43,11 @@ jobs:
- name: Check out repo
uses: actions/checkout@v2
- name: Run prettier
- uses: EPMatt/reviewdog-action-prettier@v1
- with:
- github_token: ${{ secrets.github_token }}
- reporter: github-pr-review
- level: warning
- command: npx prettier -c 'i18n/**/*.{js,jsx,ts,tsx,css,md,json}'
+ run: |-
+ npx prettier -c 'i18n/**/*.{js,jsx,ts,tsx,css,md,json}'
+ # uses: EPMatt/reviewdog-action-prettier@v1
+ # with:
+ # github_token: ${{ secrets.github_token }}
+ # reporter: github-pr-review
+ # level: warning
+ # command: npx prettier -c 'i18n/**/*.{js,jsx,ts,tsx,css,md,json}'
diff --git a/.github/workflows/studio-build.yml b/.github/workflows/studio-build.yml
index 570baa1aa61..35d1f97b8b1 100644
--- a/.github/workflows/studio-build.yml
+++ b/.github/workflows/studio-build.yml
@@ -7,6 +7,7 @@ on:
- studio
paths:
- 'studio/**'
+ - 'package-lock.json'
jobs:
build:
runs-on: ubuntu-latest
diff --git a/.github/workflows/studio-tests.yml b/.github/workflows/studio-tests.yml
index 1a99a50182d..72a84549568 100644
--- a/.github/workflows/studio-tests.yml
+++ b/.github/workflows/studio-tests.yml
@@ -8,10 +8,12 @@ on:
branches: [master]
paths:
- 'studio/**'
+ - 'package-lock.json'
pull_request:
branches: [master]
paths:
- 'studio/**'
+ - 'package-lock.json'
jobs:
build:
runs-on: ubuntu-latest
diff --git a/.gitignore b/.gitignore
index f536bae18d7..c67a911193a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,6 +4,7 @@ node_modules
out
.docz
tmp
+.swp
coverage
allure-results
@@ -119,4 +120,4 @@ typings/
**/supabase/.branches
**/supabase/.temp
-apps/new-docs/*
\ No newline at end of file
+apps/new-docs/*
diff --git a/DEVELOPERS.md b/DEVELOPERS.md
index 0281b1b0bd7..1845aa29ca5 100644
--- a/DEVELOPERS.md
+++ b/DEVELOPERS.md
@@ -57,7 +57,7 @@ To contribute code to [Supabase](https://supabase.com), you must fork the [Supab
[Supabase](https://supabase.com) uses [Turborepo](https://turborepo.org/docs) to manage and run this monorepo.
-1. Install the dependences in the root of the repo.
+1. Install the dependencies in the root of the repo.
```sh
npm install # install dependencies
@@ -113,7 +113,7 @@ Following the changes to the [Supabase docs](https://supabase.com/blog/new-supab
- Inside of `apps/docs` create a `.env.local` file with the following: `NEXT_PUBLIC_NEW_DOCS=true`
-Now when you run a local devlopment docs server you will see the new docs site.
+Now when you run a local development docs server you will see the new docs site.
---
diff --git a/README.md b/README.md
index f4c8b99e41f..d6498236c88 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
-
-
+
+
---
diff --git a/about/.gitignore b/about/.gitignore
deleted file mode 100644
index 9b744063823..00000000000
--- a/about/.gitignore
+++ /dev/null
@@ -1,3 +0,0 @@
-node_modules
-build
-.docusaurus
diff --git a/about/README.md b/about/README.md
deleted file mode 100644
index faaa71bd879..00000000000
--- a/about/README.md
+++ /dev/null
@@ -1,10 +0,0 @@
-# Supabase Docs
-
-### Contributing to the specs
-
-- `cd /web/spec/`
-- run `make` which will pull the latest TSDoc with your updates
-- add your comments to `supabase.yml` | `postgrest.yml` | `gotrue.yml` | `realtime.yml`
-- run `npm run gen:supabase` inside the `/spec` folder which will generate the Docusaurus pages
-- run `npm run build` to make sure the build is working
-- submit your PR
diff --git a/about/docs/careers.mdx b/about/docs/careers.mdx
deleted file mode 100644
index c2afe678804..00000000000
--- a/about/docs/careers.mdx
+++ /dev/null
@@ -1,7 +0,0 @@
----
-id: careers
-title: Work at Supabase
-description: We're hiring. Start your career at Supabase.
----
-
-[Browse open roles](https://boards.greenhouse.io/supabase).
diff --git a/about/docs/careers/_snippets/about.mdx b/about/docs/careers/_snippets/about.mdx
deleted file mode 100644
index d86aaefe294..00000000000
--- a/about/docs/careers/_snippets/about.mdx
+++ /dev/null
@@ -1,3 +0,0 @@
-Supabase is an open source Firebase alternative. We're backed by Y Combinator, Mozilla, Coatue, and a bunch of [amazing developers](https://supabase.com/blog/angels-of-supabase).
-
-Supabase is a platform which makes it incredibly easy to build _and_ scale your projects.
diff --git a/about/docs/careers/_snippets/apply.mdx b/about/docs/careers/_snippets/apply.mdx
deleted file mode 100644
index aaedbe92954..00000000000
--- a/about/docs/careers/_snippets/apply.mdx
+++ /dev/null
@@ -1 +0,0 @@
-Apply for this role using our [online form](https://boards.greenhouse.io/supabase).
\ No newline at end of file
diff --git a/about/docs/careers/_snippets/hiring-freeze.mdx b/about/docs/careers/_snippets/hiring-freeze.mdx
deleted file mode 100644
index 89ce1edca27..00000000000
--- a/about/docs/careers/_snippets/hiring-freeze.mdx
+++ /dev/null
@@ -1,3 +0,0 @@
-:::note
-We're currently on a Hiring Freeze and only open to very specific roles. You're still welcome to apply, but we might take a while to respond!
-:::
\ No newline at end of file
diff --git a/about/docs/careers/_snippets/process.mdx b/about/docs/careers/_snippets/process.mdx
deleted file mode 100644
index 8dd01678a82..00000000000
--- a/about/docs/careers/_snippets/process.mdx
+++ /dev/null
@@ -1,11 +0,0 @@
-
-- We hire in batches, but accept applications on a rolling basis, so now is always a good time to apply :)
-- The entire process is fully remote and all communication will happen over email or via video chat.
-- Once you've submitted your application, the team will review your submission, and may reach out for a short screening interview over video call.
-- If you pass the screen you will be invited to up to four follow up interviews. The calls:
- - usually take between 20-45 minutes each depending on the interviewer
- - are all 1:1
- - will be with both founders, a member of either the growth or engineering team (depending on the role), and usually one other person from your immediate team or function.
-- Once the interviews are over, the team will meet to discuss several roles and candidates and may:
- - ask one or two follow-up questions over email or a quick call
- - go directly to making an offer
\ No newline at end of file
diff --git a/about/docs/careers/_snippets/roles.mdx b/about/docs/careers/_snippets/roles.mdx
deleted file mode 100644
index 756d3256227..00000000000
--- a/about/docs/careers/_snippets/roles.mdx
+++ /dev/null
@@ -1,91 +0,0 @@
-
-
-
diff --git a/apps/docs/lib/constants.ts b/apps/docs/lib/constants.ts
new file mode 100644
index 00000000000..7fff3fa4410
--- /dev/null
+++ b/apps/docs/lib/constants.ts
@@ -0,0 +1 @@
+export const IS_PLATFORM = process.env.NEXT_PUBLIC_IS_PLATFORM === 'true'
diff --git a/apps/docs/next.config.mjs b/apps/docs/next.config.mjs
index 003bb8c1340..777b2e2224a 100644
--- a/apps/docs/next.config.mjs
+++ b/apps/docs/next.config.mjs
@@ -53,6 +53,7 @@ const nextConfig = {
'github.com',
'user-images.githubusercontent.com',
'raw.githubusercontent.com',
+ 'weweb-changelog.ghost.io',
],
},
experimental: {
diff --git a/apps/docs/package.json b/apps/docs/package.json
index a8c42541d9e..717e8d05d28 100644
--- a/apps/docs/package.json
+++ b/apps/docs/package.json
@@ -4,6 +4,7 @@
"private": true,
"scripts": {
"dev": "next dev --port 3001",
+ "dev:secrets:pull": "AWS_PROFILE=supabase-dev node internals/getSecrets.js",
"build": "npm run generate:all && next build",
"start": "next start",
"lint": "next lint",
diff --git a/apps/docs/pages/_app.tsx b/apps/docs/pages/_app.tsx
index e73aa39d38f..4a3f65a5680 100644
--- a/apps/docs/pages/_app.tsx
+++ b/apps/docs/pages/_app.tsx
@@ -10,6 +10,7 @@ import { SearchProvider } from '~/components/DocSearch'
import Favicons from '~/components/Favicons'
import SiteLayout from '~/layouts/SiteLayout'
import { post } from '~/lib/fetchWrappers'
+import { IS_PLATFORM } from '~/lib/constants'
import '../styles/algolia-search.scss'
import '../styles/ch.scss'
import '../styles/docsearch.scss'
@@ -19,7 +20,8 @@ import '../styles/prism-okaidia.scss'
function MyApp({ Component, pageProps }: AppPropsWithLayout) {
const router = useRouter()
- const [supabase] = useState(() => createBrowserSupabaseClient())
+
+ const [supabase] = useState(() => (IS_PLATFORM ? createBrowserSupabaseClient() : undefined))
function telemetry(route: string) {
return post(`https://api.supabase.io/platform/telemetry/page`, {
@@ -85,7 +87,19 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) {
cardType: 'summary_large_image',
}}
/>
-
+ {IS_PLATFORM ? (
+
+
+
+
+
+
+
+
+
+
+
+ ) : (
@@ -95,7 +109,7 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) {
-
+ )}
>
)
}
diff --git a/apps/docs/pages/guides/auth/auth-helpers/sveltekit.mdx b/apps/docs/pages/guides/auth/auth-helpers/sveltekit.mdx
index bcdcef28fc2..7598037b2d8 100644
--- a/apps/docs/pages/guides/auth/auth-helpers/sveltekit.mdx
+++ b/apps/docs/pages/guides/auth/auth-helpers/sveltekit.mdx
@@ -257,7 +257,7 @@ Wrap an Action to check that the user has a valid session. If they're not logged
```ts title=src/routes/posts/+page.server.ts
import type { Actions } from './$types'
import { getSupabase } from '@supabase/auth-helpers-sveltekit'
-import { error, invalid } from '@sveltejs/kit'
+import { error, fail } from '@sveltejs/kit'
export const actions: Actions = {
createPost: async (event) => {
@@ -276,7 +276,7 @@ export const actions: Actions = {
.insert({ content })
if (createPostError) {
- return invalid(500, {
+ return fail(500, {
supabaseErrorMessage: createPostError.message,
})
}
@@ -293,7 +293,7 @@ If you try to submit a form with the action `?/createPost` without a valid sessi
```ts
import type { Actions } from './$types'
-import { invalid, redirect } from '@sveltejs/kit'
+import { fail, redirect } from '@sveltejs/kit'
import { getSupabase } from '@supabase/auth-helpers-sveltekit'
import { AuthApiError } from '@supabase/supabase-js'
@@ -313,14 +313,14 @@ export const actions: Actions = {
if (error) {
if (error instanceof AuthApiError && error.status === 400) {
- return invalid(400, {
+ return fail(400, {
error: 'Invalid credentials.',
values: {
email,
},
})
}
- return invalid(500, {
+ return fail(500, {
error: 'Server error. Try again later.',
values: {
email,
diff --git a/apps/docs/pages/guides/auth/managing-user-data.mdx b/apps/docs/pages/guides/auth/managing-user-data.mdx
index bd5ae4f7db7..462f160b238 100644
--- a/apps/docs/pages/guides/auth/managing-user-data.mdx
+++ b/apps/docs/pages/guides/auth/managing-user-data.mdx
@@ -87,6 +87,32 @@ If you need to fetch a full list of user profiles, we supply a `service_key` whi
Make sure you _NEVER_ expose this publicly. But it can be used on the server-side to fetch all of the profiles.
+## Accessing User Metadata
+
+You can assign metadata to users on sign up:
+
+```js
+const { data, error } = await supabase.auth.signUp({
+ email: 'example@email.com',
+ password: 'example-password',
+ options: {
+ data: {
+ first_name: 'John',
+ age: 27,
+ },
+ },
+})
+```
+
+User metadata is stored on the `raw_user_meta_data` column of the `auth.users` table. To view the metadata:
+
+```js
+const {
+ data: { user },
+} = await supabase.auth.getUser()
+let metadata = user.user_metadata
+```
+
## Advanced techniques
### Using triggers
diff --git a/apps/docs/pages/guides/auth/overview.mdx b/apps/docs/pages/guides/auth/overview.mdx
index 07cbc79859f..2e6760545b4 100644
--- a/apps/docs/pages/guides/auth/overview.mdx
+++ b/apps/docs/pages/guides/auth/overview.mdx
@@ -44,13 +44,21 @@ You can authenticate your users in several ways:
We provide a suite of Providers and login methods, as well as [Auth helpers](/docs/guides/auth/auth-helpers/).
+#### Social Auth
+
-
+
+
+
+#### Phone Auth
+
+
+
### Configure third-party providers
-You can enable third-party providers with the click of a button by navigating to Authentication > Settings > Auth Providers and inputting your `Client ID` and `Secret` for each.
+You can enable third-party providers with the click of a button by navigating to Authentication > Providers > Auth Providers and inputting your `Client ID` and `Secret` for each.

diff --git a/apps/docs/pages/guides/database/api.mdx b/apps/docs/pages/guides/database/api.mdx
index e31cafee9aa..fc8f86a18ae 100644
--- a/apps/docs/pages/guides/database/api.mdx
+++ b/apps/docs/pages/guides/database/api.mdx
@@ -209,12 +209,12 @@ curl '/rest/v1/todos' \
-JS Reference: [select()](../reference/javascript/select),
-[insert()](../reference/javascript/insert),
-[update()](../reference/javascript/update),
-[upsert()](../reference/javascript/upsert),
-[delete()](../reference/javascript/delete),
-[rpc()](../reference/javascript/rpc) (call Postgres functions).
+JS Reference: [select()](/docs/reference/javascript/select),
+[insert()](/docs/reference/javascript/insert),
+[update()](/docs/reference/javascript/update),
+[upsert()](/docs/reference/javascript/upsert),
+[delete()](/docs/reference/javascript/delete),
+[rpc()](/docs/reference/javascript/rpc) (call Postgres functions).
### GraphQL API
diff --git a/apps/docs/pages/guides/database/extensions.mdx b/apps/docs/pages/guides/database/extensions.mdx
index 1c10af0d6bd..eff1165b8fd 100644
--- a/apps/docs/pages/guides/database/extensions.mdx
+++ b/apps/docs/pages/guides/database/extensions.mdx
@@ -23,24 +23,18 @@ Supabase has pre-installed some of the most useful open source extensions.
2. Click **Extensions** in the sidebar.
3. Enable or disable an extension.
-
-
```sql
--- Example: enable the "pgtap" extension and ensure it is installed
-create extension pgtap with schema extensions;
+ -- Example: enable the "pgtap" extension and ensure it is installed
+create extension pgtap
+with
+ schema extensions;
-- Example: disable the "pgtap" extension
-drop extension pgtap;
+drop
+ extension pgtap;
```
Even though the SQL code is `create extension`, this is the equivalent of "enabling the extension".
diff --git a/apps/docs/pages/guides/database/extensions/http.mdx b/apps/docs/pages/guides/database/extensions/http.mdx
index 11ac8e55d41..7f686ed8e7b 100644
--- a/apps/docs/pages/guides/database/extensions/http.mdx
+++ b/apps/docs/pages/guides/database/extensions/http.mdx
@@ -63,7 +63,7 @@ drop extension if exists http;
Even though the SQL code is `create extension`, this is the equivalent of "enabling the extension".
To disable an extension, call `drop extension`.
-It's good practice to create the extension within a separate schema (like `extensions`) to keep your database clean.
+It's good practice to create the extension within a separate schema (like `extensions`) to keep the `public` schema clean.
diff --git a/apps/docs/pages/guides/database/extensions/hypopg.mdx b/apps/docs/pages/guides/database/extensions/hypopg.mdx
index c493f9f89cd..18688e276e4 100644
--- a/apps/docs/pages/guides/database/extensions/hypopg.mdx
+++ b/apps/docs/pages/guides/database/extensions/hypopg.mdx
@@ -7,11 +7,10 @@ export const meta = {
}
`HypoPG` is PostgreSQL extension for creating hypothetical/virtual indexes. HypoPG allows users to rapidly create hypothetical/virtual indexes that have no resource cost (CPU, disk, memory) that are visible to the PostgreSQL query planner.
-That allows users to quickly search for an index to improve a slow query without waiting for them to build.
-## Usage
+The motivation for HypoPG is to allow users to quickly search for an index to improve a slow query without consuming server resources or waiting for them to build.
-### Enable the extension
+## Enable the extension
### Speeding up a query
-Given the following table and a simple query to select from the table by id:
+Given the following table and a simple query to select from the table by `id`:
{/* prettier-ignore */}
```sql
diff --git a/apps/docs/pages/guides/database/extensions/pg-safeupdate.mdx b/apps/docs/pages/guides/database/extensions/pg-safeupdate.mdx
new file mode 100644
index 00000000000..33773977b55
--- /dev/null
+++ b/apps/docs/pages/guides/database/extensions/pg-safeupdate.mdx
@@ -0,0 +1,60 @@
+import Layout from '~/layouts/DefaultGuideLayout'
+
+export const meta = {
+ id: 'pg-safeupdate',
+ title: 'pg-safeupdate: Required Where Clauses',
+ description: 'Requires a where clause for update and delete',
+}
+
+[pg-safeupdate](https://github.com/eradman/pg-safeupdate) is a PostgreSQL extension designed to prevent users from accidentally updating or deleting too many records in a single statement by requiring a "where" clause in all update and delete statements.
+
+The `pg-safeupdate` extension is a useful tool for protecting data integrity and preventing accidental data loss. Without it, a user could accidentally execute an update or delete statement that affects all records in a table. With `pg-safeupdate`, users are required to be more deliberate in their update and delete statements, which reduces the risk of significant error.
+
+## Enable the extension
+
+`pg-safeupdate` can be enabled on a per connection basis:
+
+{/* prettier-ignore */}
+```sql
+load 'safeupdate';
+```
+
+or for all connections:
+
+{/* prettier-ignore */}
+```sql
+alter database some_db set session_preload_libraries = 'safeupdate';
+```
+
+## Usage
+
+Let's take a look at an example to see how pg-safeupdate works. Suppose we have a table called `employees` with the following columns: `id`, `name`, `department`, and `date_of_birth`. We want to update the `date_of_birth` for a specific employee with the `id` of 12345. Here is what the query would look like if we forgot to add a "where" clause:
+
+{/* prettier-ignore */}
+```sql
+load 'safeupdate';
+
+update employees set date_of_birth = '1987-01-28';
+```
+
+This query updates the `date_of_birth` for all employees to 1987-01-28, which is not what we intended. With `pg-safeupdate` enabled, we receive an error message prompting us to add a "where" clause to the query:
+
+{/* prettier-ignore */}
+```sql
+ERROR: UPDATE requires a WHERE clause
+```
+
+We would then update our query to include a "where" clause specifying the employee with the `id` of 12345:
+
+{/* prettier-ignore */}
+```sql
+update employees set date_of_birth = '1987-01-28' where id = 12345;
+```
+
+## Resources
+
+- Official [pg-safeupdate documentation](https://github.com/eradman/pg-safeupdate)
+
+export const Page = ({ children }) =>
+
+export default Page
diff --git a/apps/docs/pages/guides/database/extensions/pg_graphql.mdx b/apps/docs/pages/guides/database/extensions/pg_graphql.mdx
new file mode 100644
index 00000000000..a63be68c164
--- /dev/null
+++ b/apps/docs/pages/guides/database/extensions/pg_graphql.mdx
@@ -0,0 +1,116 @@
+import Layout from '~/layouts/DefaultGuideLayout'
+
+export const meta = {
+ id: 'pg_graphql',
+ title: 'pg_graphql: GraphQL for PostgreSQL',
+ description: 'A GraphQL Interface for PostgreSQL',
+}
+
+[pg_graphql](https://supabase.github.io/pg_graphql/) is PostgreSQL extension for interacting with the database using [GraphQL](https://graphql.org) instead of SQL.
+
+The extension reflects a GraphQL schema from the existing SQL schema and exposes it through a SQL function, `graphql.resolve(...)`. This enables any programming language that can connect to PostgreSQL to query the database via GraphQL with no additional servers, processes, or libraries.
+
+The `pg_graphql` resolve method is designed to interop with [PostgREST](https://postgrest.org/en/stable/index.html), the tool that underpins the Supabase API, such that the `graphql.resolve` function can be called via RPC to safely and performantly expose the GraphQL API over HTTP/S.
+
+For more information about how the SQL schema is reflected into a GraphQL schema, see the [pg_graphql API docs](https://supabase.github.io/pg_graphql/api/).
+
+## Enable the extension
+
+
+
+
+1. Go to the [Database](https://app.supabase.com/project/_/database/tables) page in the Dashboard.
+2. Click on **Extensions** in the sidebar.
+3. Search for "pg_graphql" and enable the extension.
+
+
+
+
+{/* prettier-ignore */}
+```sql
+-- Enable the "pg_graphql" extension
+create extension pg_graphql;
+
+-- Disable the "pg_graphql" extension
+drop extension if exists pg_graphql;
+```
+
+Even though the SQL code is `create extension`, this is the equivalent of "enabling the extension".
+To disable an extension you can call `drop extension`.
+
+
+
+
+## Usage
+
+Given a table
+
+{/* prettier-ignore */}
+```sql
+create table "Blog"(
+ id serial primary key,
+ name varchar(255) not null,
+ description varchar(255),
+);
+
+insert into "Blog"(name)
+values ('My Blog');
+```
+
+The reflected GraphQL schema can be queries immediately as
+
+{/* prettier-ignore */}
+```sql
+select
+ graphql.resolve($$
+ {
+ blogCollection(first: 1) {
+ edges {
+ node {
+ id,
+ name
+ }
+ }
+ }
+ }
+ $$);
+```
+
+returning the JSON
+
+{/* prettier-ignore */}
+```json
+{
+ "data": {
+ "blogCollection": {
+ "edges": [
+ {
+ "node": {
+ "id": 1
+ "name": "My Blog"
+ }
+ }
+ ]
+ }
+ }
+}
+```
+
+Note that `pg_graphql` fully supports schema introspection so you can connect any GraphQL IDE or schema inspection tool to see the full set of fields and arguments available in the API.
+
+## API
+
+- [`graphql.resolve`](https://supabase.github.io/pg_graphql/sql_interface/): A SQL function for executing GraphQL queries.
+
+## Resources
+
+- Official [`pg_graphql` documentation](https://github.com/supabase/pg_graphql)
+
+export const Page = ({ children }) =>
+
+export default Page
diff --git a/apps/docs/pages/guides/database/extensions/pg_hashids.mdx b/apps/docs/pages/guides/database/extensions/pg_hashids.mdx
new file mode 100644
index 00000000000..ad02c19b1ff
--- /dev/null
+++ b/apps/docs/pages/guides/database/extensions/pg_hashids.mdx
@@ -0,0 +1,81 @@
+import Layout from '~/layouts/DefaultGuideLayout'
+
+export const meta = {
+ id: 'pg_hashids',
+ title: 'pg_hashids: Short UIDs',
+ description: 'Generate Short UIDs from Numbers',
+}
+
+[pg_hashids](https://github.com/iCyberon/pg_hashids) provides a secure way to generate short, unique, non-sequential ids from numbers. The hashes are intended to be small, easy-to-remember identifiers that can be used to obfuscate data (optionally) with a password, alphabet, and salt. For example, you may wish to hide data like user IDs, order numbers, or tracking codes in favor of `pg_hashid`'s unique identifers.
+
+## Enable the Extension
+
+
+
+
+1. Go to the [Database](https://app.supabase.com/project/_/database/tables) page in the Dashboard.
+2. Click on **Extensions** in the sidebar.
+3. Search for "pg_hashids" and enable the extension.
+
+
+
+
+{/* prettier-ignore */}
+```sql
+-- Enable the "pg_hashids" extension
+create extension pg_hashids with schema extensions;
+
+-- Disable the "pg_hashids" extension
+drop extension if exists pg_hashids;
+```
+
+Even though the SQL code is `create extension`, this is the equivalent of "enabling the extension".
+To disable an extension you can call `drop extension`.
+
+It's good practice to create the extension within a separate schema (like `extensions`) to keep your `public` schema clean.
+
+
+
+
+## Usage
+
+Suppose we have a table that stores order information, and we want to give customers a unique identifer without exposing the sequential `id` column. To do this, we can use `pg_hashid`'s `id_encode` function.
+
+```sql
+create table orders (
+ id serial primary key,
+ description text,
+ price_cents bigint
+);
+
+insert into orders (description, price_cents)
+values ('a book', 9095);
+
+select
+ id,
+ id_encode(id) as short_id,
+ description,
+ price_cents
+from
+ orders;
+
+ id | short_id | description | price_cents
+----+----------+-------------+-------------
+ 1 | jR | a book | 9095
+(1 row)
+```
+
+To reverse the `short_id` back into an `id`, there is an equivalent function named `id_decode`.
+
+## Resources
+
+- Official [pg_hashids documention](https://github.com/iCyberon/pg_hashids)
+
+export const Page = ({ children }) =>
+
+export default Page
diff --git a/apps/docs/pages/guides/database/extensions/pg_jsonschema.mdx b/apps/docs/pages/guides/database/extensions/pg_jsonschema.mdx
index c51a4c3783e..4cf54f21a19 100644
--- a/apps/docs/pages/guides/database/extensions/pg_jsonschema.mdx
+++ b/apps/docs/pages/guides/database/extensions/pg_jsonschema.mdx
@@ -6,11 +6,9 @@ export const meta = {
description: 'Validate json/jsonb with JSON Schema in PostgreSQL.',
}
-[JSON Schema](https://json-schema.org) is a language for annotating and validating JSON documents. [`pg_jsonschema`](https://json-schema.org) is a PostgreSQL extension that adds the ability to validate PostgreSQL's built-in `json` and `jsonb` data types against a JSON Schema document.
+[JSON Schema](https://json-schema.org) is a language for annotating and validating JSON documents. [`pg_jsonschema`](https://github.com/supabase/pg_jsonschema) is a PostgreSQL extension that adds the ability to validate PostgreSQL's built-in `json` and `jsonb` data types against JSON Schema documents.
-## Usage
-
-### Enable the extension
+## Enable the extension
@@ -49,9 +47,9 @@ It's good practice to create the extension within a separate schema (like `exten
- [`json_matches_schema(schema json, instance json)`](https://github.com/supabase/pg_jsonschema#api): Checks if a `json` _instance_ conforms to a JSON Schema _schema_.
- [`jsonb_matches_schema(schema json, instance jsonb)`](https://github.com/supabase/pg_jsonschema#api): Checks if a `jsonb` _instance_ conforms to a JSON Schema _schema_.
-### Validating a document
+## Usage
-Since `pg_jsonschema` exposes its utilities as functions, we can test it with a simple select statement:
+Since `pg_jsonschema` exposes its utilities as functions, we can execute them with a select statement:
{/* prettier-ignore */}
```sql
@@ -62,7 +60,7 @@ select
);
```
-`pg_jsonschema` is generally used in tandem with a [check constraint](https://www.postgresql.org/docs/current/ddl-constraints.html) as a way to constrain the contents of a json/b field to match a JSON Schema.
+`pg_jsonschema` is generally used in tandem with a [check constraint](https://www.postgresql.org/docs/current/ddl-constraints.html) as a way to constrain the contents of a json/b column to match a JSON Schema.
{/* prettier-ignore */}
```sql
diff --git a/apps/docs/pages/guides/database/extensions/pg_plan_filter.mdx b/apps/docs/pages/guides/database/extensions/pg_plan_filter.mdx
new file mode 100644
index 00000000000..ab46d931ffe
--- /dev/null
+++ b/apps/docs/pages/guides/database/extensions/pg_plan_filter.mdx
@@ -0,0 +1,97 @@
+import Layout from '~/layouts/DefaultGuideLayout'
+
+export const meta = {
+ id: 'pg_plan_filter',
+ title: 'pg_plan_filter: Restrict Total Cost',
+ description: 'Block queries over a total cost limit',
+}
+
+[pg_plan_filter](https://github.com/pgexperts/pg_plan_filter) is PostgreSQL extension to block execution of statements where query planner's estimate of the total cost exceeds a threshold. This is intended to give database administrators a way to restrict the contribution an individual query has on database load.
+
+## Enable the extension
+
+`pg_plan_filter` can be enabled on a per connection basis:
+
+{/* prettier-ignore */}
+```sql
+load 'plan_filter';
+```
+
+or for all connections:
+
+{/* prettier-ignore */}
+```sql
+alter database some_db set session_preload_libraries = 'plan_filter';
+```
+
+## API
+
+`plan_filter.statement_cost_limit`: restricts the maximum total cost for executed statements
+`plan_filter.limit_select_only`: restricts to `select` statements
+
+Note that `limit_select_only = true` is not the same as read-only because `select` statements may modfiy data e.g. through a function call.
+
+## Example
+
+To demonstrate total cost filtering, we'll compare how `plan_filter.statement_cost_limit` treats queries that are under and over its cost limit. First, we set up a table with some data:
+
+{/* prettier-ignore */}
+```sql
+create table book(
+ id int primary key
+);
+-- CREATE TABLE
+
+insert into book(id) select * from generate_series(1, 10000);
+-- INSERT 0 10000
+```
+
+Next, we can review the explain plans for a single record select, and a whole table select.
+
+{/* prettier-ignore */}
+```sql
+explain select * from book where id =1;
+ QUERY PLAN
+---------------------------------------------------------------------------
+ Index Only Scan using book_pkey on book (cost=0.28..2.49 rows=1 width=4)
+ Index Cond: (id = 1)
+(2 rows)
+
+explain select * from book;
+ QUERY PLAN
+---------------------------------------------------------
+ Seq Scan on book (cost=0.00..135.00 rows=10000 width=4)
+(1 row)
+```
+
+Now we can choose a `statement_cost_filter` value between the total cost for the single select (2.49) and the whole table select (135.0) so one statement will succeed and one will fail.
+
+{/* prettier-ignore */}
+```sql
+load 'plan_filter';
+set plan_filter.statement_cost_limit = 50; -- between 2.49 and 135.0
+
+select * from book where id = 1;
+ id
+----
+ 1
+(1 row)
+-- SUCCESS
+```
+
+{/* prettier-ignore */}
+```sql
+select * from book;
+
+ERROR: plan cost limit exceeded
+HINT: The plan for your query shows that it would probably have an excessive run time. This may be due to a logic error in the SQL, or it maybe just a very costly query. Rewrite your query or increase the configuration parameter "plan_filter.statement_cost_limit".
+-- FAILURE
+```
+
+## Resources
+
+- Official [pg_plan_filter documentation](https://github.com/pgexperts/pg_plan_filter)
+
+export const Page = ({ children }) =>
+
+export default Page
diff --git a/apps/docs/pages/guides/database/extensions/pg_stat_monitor.mdx b/apps/docs/pages/guides/database/extensions/pg_stat_monitor.mdx
new file mode 100644
index 00000000000..4c1363b83b9
--- /dev/null
+++ b/apps/docs/pages/guides/database/extensions/pg_stat_monitor.mdx
@@ -0,0 +1,88 @@
+import Layout from '~/layouts/DefaultGuideLayout'
+
+export const meta = {
+ id: 'pg_stat_monitor',
+ title: 'pg_stat_monitor: Query Performance Monitoring',
+ description: 'Query performance monitoring for PostgreSQL',
+}
+
+[pg_stat_monitor](https://github.com/percona/pg_stat_monitor) is query performance monitoring tool that provides query execution statistics in a SQL view named `pg_stat_monitor`. It tracks a superset of statistics available in [`pg_stat_statements`](https://www.postgresql.org/docs/current/pgstatstatements.html). Some of the most useful features are:
+
+- **Time Interval Grouping**: configurable time buckets to track query usage over time
+- **Capture Parameters**: optionally track parameters passed into queries instead of generic placeholders e.g. `$1`
+- **Query Plan**: store query plans used for execution
+
+For more information on query optimization, check out the [query performance guide](/docs/guides/platform/performance#examining-query-performance).
+
+## Enable the extension
+
+
+
+
+1. Go to the [Database](https://app.supabase.com/project/_/database/tables) page in the Dashboard.
+2. Click on **Extensions** in the sidebar.
+3. Search for "pg_stat_monitor" and enable the extension.
+
+
+
+
+{/* prettier-ignore */}
+```sql
+-- Enable the "pg_stat_monitor" extension
+create extension pg_stat_monitor with schema extensions;
+
+-- Disable the "pg_stat_monitor" extension
+drop extension if exists pg_stat_monitor;
+```
+
+Even though the SQL code is `create extension`, this is the equivalent of "enabling the extension".
+To disable an extension you can call `drop extension`.
+
+It's good practice to create the extension within a separate schema (like `extensions`) to keep the `public` schema clean.
+
+
+
+
+## Views
+
+{/* prettier-ignore */}
+```sql
+select * from extensions.pg_stat_monitor;
+```
+
+The following table shows a subset of available columns:
+
+| Column Type | Description |
+| ------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
+| bucket bigint | Data collection unit. The number shows what bucket in a chain a record belongs to |
+| bucket_start_time timestampz | The start time of the bucket |
+| userid oid (references pg_authid.oid) | OID of user who executed the statement |
+| dbid oid (references pg_database.oid) | OID of database in which the statement was executed |
+| toplevel bool | True if the query was executed as a top-level statement (always true if pg_stat_statements.track is set to top) |
+| client_ip inet | The IP address of a client that ran the query |
+| queryid bigint | Hash code to identify identical normalized queries. |
+| planid text | An internally generated ID of a query plan |
+| query_plan text | The sequence of steps used to execute a query. This parameter is only available when pgsm_enable_query_plan is enabled |
+| query text | Text of a representative statement |
+| plans bigint | Number of times the statement was planned (if pg_stat_statements.track_planning is enabled, otherwise zero) |
+| total_plan_time double precision | Total time spent planning the statement, in milliseconds (if pg_stat_statements.track_planning is enabled, otherwise zero) |
+| min_plan_time double precision | Minimum time spent planning the statement, in milliseconds (if pg_stat_statements.track_planning is enabled, otherwise zero) |
+
+A full list of statistics is available in the [pg_stat_monitor docs](https://docs.percona.com/pg-stat-monitor/reference.html#postgresql-15).
+
+## Functions
+- [`pg_stat_monitor_reset()`](https://docs.percona.com/pg-stat-monitor/functions.html): Resets the statistics tracked by the `pg_stat_monitor` view and deletes all previous data.
+- [`pg_stat_monitor_version()`](https://docs.percona.com/pg-stat-monitor/functions.html): Displays the version of the `pg_stat_monitor` extension.
+
+## Resources
+
+- Official [`pg_stat_monitor` documentation](https://github.com/percona/pg_stat_monitor)
+
+export const Page = ({ children }) =>
+
+export default Page
diff --git a/apps/docs/pages/guides/database/extensions/pg_stat_statements.mdx b/apps/docs/pages/guides/database/extensions/pg_stat_statements.mdx
index 711878d78c0..cb64a901b0c 100644
--- a/apps/docs/pages/guides/database/extensions/pg_stat_statements.mdx
+++ b/apps/docs/pages/guides/database/extensions/pg_stat_statements.mdx
@@ -2,7 +2,7 @@ import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'pg_stat_statements',
- title: 'pg_stat_statements: SQL Planning and Execution Statistics',
+ title: 'pg_stat_statements: Query Performance Monitoring',
description:
'Track planning and execution statistics of all SQL statements executed on the database.',
}
@@ -22,9 +22,9 @@ export const meta = {
A full list of statistics is available in the [pg_stat_statements docs](https://www.postgresql.org/docs/current/pgstatstatements.html).
-## Usage
+For more information on query optimization, check out the [query performance guide](/docs/guides/platform/performance#examining-query-performance).
-### Enable the extension
+## Enable the extension
-### Inspecting activity
+## Inspecting activity
A common use for `pg_stat_statements` is to track down expensive or slow queries. The `pg_stat_statements` view contains a row for each executed query with statistics inlined. For example, you can leverage the statistics to identify frequently executed and slow queries against a given table.
@@ -74,15 +74,15 @@ select
from
pg_stat_statements
where
- calls > 1000 -- at least 50 calls
- and mean_exec_time > 2.0 -- averaging at least 2ms/call
- and total_exec_time > 60000 -- at least one minute total server time spent
- and query ilike '%user_in_organization%' -- filter to queries that touch the user_in_organization table
+ calls > 50 -- at least 50 calls
+ and mean_exec_time > 2.0 -- averaging at least 2ms/call
+ and total_exec_time > 60000 -- at least one minute total server time spent
+ and query ilike '%user_in_organization%' -- filter to queries that touch the user_in_organization table
order by
calls desc
```
-From the results, an informed decision about which queries to optimize, index, or adjust can be made.
+From the results, we can make an informed decision about which queries to optimize or index.
## Resources
diff --git a/apps/docs/pages/guides/database/extensions/pgaudit.mdx b/apps/docs/pages/guides/database/extensions/pgaudit.mdx
new file mode 100644
index 00000000000..0352fb6d131
--- /dev/null
+++ b/apps/docs/pages/guides/database/extensions/pgaudit.mdx
@@ -0,0 +1,105 @@
+import Layout from '~/layouts/DefaultGuideLayout'
+
+export const meta = {
+ id: 'pgaudit',
+ title: 'PGAudit: Postgres Auditing',
+ description: 'Session and object auditing via PostgreSQL standard logging',
+}
+
+[PGAudit](https://www.pgaudit.org) is a PostgreSQL extension for logging session and object auditing over the standard PostgreSQL logging utility.
+
+PGAudit grants fine grain control over which statements and objects are emitted to logs.
+
+## Enable the extension
+
+
+
+
+1. Go to the [Database](https://app.supabase.com/project/_/database/tables) page in the Dashboard.
+2. Click on **Extensions** in the sidebar.
+3. Search for "pgaudit" and enable the extension.
+
+
+
+
+{/* prettier-ignore */}
+```sql
+-- Enable the "pgaudit" extension
+create extension pgaudit;
+
+-- Disable the "pgaudit" extension
+drop extension if exists pgaudit;
+```
+
+Even though the SQL code is `create extension`, this is the equivalent of "enabling the extension".
+To disable an extension you can call `drop extension`.
+
+
+
+
+## Settings
+
+The `pgaudit.log` setting controls which statements to log. Available values include:
+
+- **read**: `SELECT` and `COPY` when the source is a relation or a query.
+- **write**: `INSERT`, `UPDATE`, `DELETE`, `TRUNCATE`, and `COPY` when the destination is a relation.
+- **function**: Function calls and `DO` blocks.
+- **role**: Statements related to roles and privileges: `GRANT`, `REVOKE`, `CREATE/ALTER/DROP ROLE`.
+- **ddl**: All `DDL` that is not included in the `ROLE` class.
+- **misc**: Miscellaneous commands, e.g. `DISCARD`, `FETCH`, `CHECKPOINT`, `VACUUM`, `SET`.
+- **misc_set**: Miscellaneous `SET` commands, e.g. `SET ROLE`.
+
+- **all**: Include all of the above.
+
+For a full list of available settings see [settings docs](https://github.com/pgaudit/pgaudit/blob/master/README.md#settings). Be aware that the `all` setting will generate a very large volume of logs.
+
+## Example
+
+Given a pgaudit setting
+
+{/* prettier-ignore */}
+```sql
+set pgaudit.log = 'read, ddl';
+```
+
+The following create table, insert and select statements
+
+{/* prettier-ignore */}
+```sql
+create table account (
+ id int primary key,
+ name text,
+ description text
+);
+
+insert into account (id, name, description)
+values (1, 'Foo Barsworth', 'Customer account');
+
+select * from account;
+```
+
+Results in the log output
+
+```text
+AUDIT: SESSION,1,1,DDL,CREATE TABLE,TABLE,public.account,create table account(
+ id int,
+ name text,
+ description text
+);,
+AUDIT: SESSION,2,1,READ,SELECT,,,select * from account,,
+```
+
+Note that the insert statement is not logged because we did not include the `write` option for `pgaudit.log`.
+
+## Resources
+
+- Official [`PGAudit` documentation](https://www.pgaudit.org)
+
+export const Page = ({ children }) =>
+
+export default Page
diff --git a/apps/docs/pages/guides/database/extensions/pgjwt.mdx b/apps/docs/pages/guides/database/extensions/pgjwt.mdx
new file mode 100644
index 00000000000..77c8bcc8dea
--- /dev/null
+++ b/apps/docs/pages/guides/database/extensions/pgjwt.mdx
@@ -0,0 +1,113 @@
+import Layout from '~/layouts/DefaultGuideLayout'
+
+export const meta = {
+ id: 'pgjwt',
+ title: 'pgjwt: JSON Web Tokens',
+ description: 'Encode and decode JWTs in PostgreSQL',
+}
+
+The [pgjwt](https://github.com/michelp/pgjwt) (PostgreSQL JSON Web Token) extension allows you to create and parse [JSON Web Tokens (JWTs)](https://en.wikipedia.org/wiki/JSON_Web_Token) within a PostgreSQL database. JWTs are commonly used for authentication and authorization in web applications and services.
+
+## Enable the extension
+
+
+
+
+1. Go to the [Database](https://app.supabase.com/project/_/database/tables) page in the Dashboard.
+2. Click on **Extensions** in the sidebar.
+3. Search for "pgjwt" and enable the extension.
+
+
+
+
+{/* prettier-ignore */}
+```sql
+-- Enable the "pgjwt" extension
+create extension pgjwt schema extensions;
+
+-- Disable the "pgjwt" extension
+drop extension if exists pgjwt;
+```
+
+Even though the SQL code is `create extension`, this is the equivalent of "enabling the extension".
+To disable an extension you can call `drop extension`.
+
+It's good practice to create the extension within a separate schema (like `extensions`) to keep the `public` schema clean.
+
+
+
+
+## API
+
+- [`sign(payload json, secret text, algorithm text default 'HSA256')`](https://github.com/michelp/pgjwt#usage): Signs a JWT containng _payload_ with _secret_ using _algorithm_.
+- [`verify(token text, secret text, algorithm text default 'HSA256')`](https://github.com/michelp/pgjwt#usage): Decodes a JWT _token_ that was signed with _secret_ using _algorithm_.
+
+Where:
+
+- `payload` is an encrypted JWT represented as a string.
+- `secret` is the private/secret passcode which is used to sign the JWT and verify its integrity.
+- `algorithm` is the method used to sign the JWT using the secret.
+- `token` is an encrypted JWT represented as a string.
+
+## Usage
+
+Once the extension is installed, you can use its functions to create and parse JWTs. Here's an example of how you can use the `sign` function to create a JWT:
+
+{/* prettier-ignore */}
+```sql
+select
+ extensions.sign(
+ payload := '{"sub":"1234567890","name":"John Doe","iat":1516239022}',
+ secret := 'secret',
+ algorithm := 'HS256'
+ );
+```
+
+The pgjwt_encode function returns a string that represents the JWT, which can then be safely transmitted between parties.
+
+{/* prettier-ignore */}
+```
+ sign
+---------------------------------
+ eyJhbGciOiJIUzI1NiIsInR5cCI6IkpX
+ VCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiw
+ ibmFtZSI6IkpvaG4gRG9lIiwiaWF0Ijo
+ xNTE2MjM5MDIyfQ.XbPfbIHMI6arZ3Y9
+ 22BhjWgQzWXcXNrz0ogtVhfEd2o
+(1 row)
+```
+
+To parse a JWT and extract its claims, you can use the `verify` function. Here's an example:
+
+{/* prettier-ignore */}
+```sql
+select
+ extensions.verify(
+ token := 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiRm9vIn0.Q8hKjuadCEhnCPuqIj9bfLhTh_9QSxshTRsA5Aq4IuM',
+ secret := 'secret',
+ algorithm := 'HS256'
+ );
+```
+
+Which returns the decoded contents and some associated metadata.
+
+{/* prettier-ignore */}
+```sql
+ header | payload | valid
+-----------------------------+----------------+-------
+ {"alg":"HS256","typ":"JWT"} | {"name":"Foo"} | t
+(1 row)
+```
+
+## Resources
+
+- Official [`pgjwt` documentation](https://github.com/michelp/pgjwt)
+
+export const Page = ({ children }) =>
+
+export default Page
diff --git a/apps/docs/pages/guides/database/extensions/pgnet.mdx b/apps/docs/pages/guides/database/extensions/pgnet.mdx
index 18c51a8dd2a..3246fb4f5df 100644
--- a/apps/docs/pages/guides/database/extensions/pgnet.mdx
+++ b/apps/docs/pages/guides/database/extensions/pgnet.mdx
@@ -16,9 +16,8 @@ The pg_net API is in alpha. Functions signatures may change.
It differs from the `http` extension in that it is asynchronous by default. This makes it useful in blocking functions (like triggers).
-## Usage
-### Enable the extension
+## Enable the extension
-### Creating a full text search index
+## Creating a full text search index
Given a table with a `text` column:
diff --git a/apps/docs/pages/guides/database/extensions/pgrouting.mdx b/apps/docs/pages/guides/database/extensions/pgrouting.mdx
new file mode 100644
index 00000000000..73a047fb362
--- /dev/null
+++ b/apps/docs/pages/guides/database/extensions/pgrouting.mdx
@@ -0,0 +1,159 @@
+import Layout from '~/layouts/DefaultGuideLayout'
+
+export const meta = {
+ id: 'pgrouting',
+ title: 'pgrouting: Geospatial Routing',
+ description: 'Extends PostGIS with Geospatial Routing',
+}
+
+[pgRouting](http://pgrouting.org) is PostgreSQL and [PostGIS](http://postgis.net) extension adding geospatial routing functionality.
+
+The core functionality of `pgRouting` is a set of path finding algorithms including:
+
+- All Pairs Shortest Path, Johnson’s Algorithm
+- All Pairs Shortest Path, Floyd-Warshall Algorithm
+- Shortest Path A\*
+- Bi-directional Dijkstra Shortest Path
+- Bi-directional A\* Shortest Path
+- Shortest Path Dijkstra
+- Driving Distance
+- K-Shortest Path, Multiple Alternative Paths
+- K-Dijkstra, One to Many Shortest Path
+- Traveling Sales Person
+- Turn Restriction Shortest Path (TRSP)
+
+## Enable the extension
+
+
+
+
+1. Go to the [Database](https://app.supabase.com/project/_/database/tables) page in the Dashboard.
+2. Click on **Extensions** in the sidebar.
+3. Search for "pgrouting" and enable the extension.
+
+
+
+
+{/* prettier-ignore */}
+```sql
+-- Enable the "pgRouting" extension
+create extension pgrouting cascade;
+
+-- Disable the "pgRouting" extension
+drop extension if exists pgRouting;
+```
+
+Even though the SQL code is `create extension`, this is the equivalent of "enabling the extension".
+To disable an extension you can call `drop extension`.
+
+
+
+
+## Example
+
+As an example, we'll solve the [traveling salesman probelem](https://en.wikipedia.org/wiki/Travelling_salesman_problem) using the pgRouting's `pgr_TSPeuclidean` function from some PostGIS coodinates.
+
+A summary of the traveling salesman problem is, given a set of city coordinates, solve for a path that goes through each city and minimizes the total distance traveled.
+
+First we populate a table with some X, Y coordinates
+
+{/* prettier-ignore */}
+```sql
+create table wi29 (
+ id bigint,
+ x float,
+ y float,
+ geom geometry
+);
+
+insert into wi29 (id, x, y)
+values
+ (1,20833.3333,17100.0000),
+ (2,20900.0000,17066.6667),
+ (3,21300.0000,13016.6667),
+ (4,21600.0000,14150.0000),
+ (5,21600.0000,14966.6667),
+ (6,21600.0000,16500.0000),
+ (7,22183.3333,13133.3333),
+ (8,22583.3333,14300.0000),
+ (9,22683.3333,12716.6667),
+ (10,23616.6667,15866.6667),
+ (11,23700.0000,15933.3333),
+ (12,23883.3333,14533.3333),
+ (13,24166.6667,13250.0000),
+ (14,25149.1667,12365.8333),
+ (15,26133.3333,14500.0000),
+ (16,26150.0000,10550.0000),
+ (17,26283.3333,12766.6667),
+ (18,26433.3333,13433.3333),
+ (19,26550.0000,13850.0000),
+ (20,26733.3333,11683.3333),
+ (21,27026.1111,13051.9444),
+ (22,27096.1111,13415.8333),
+ (23,27153.6111,13203.3333),
+ (24,27166.6667,9833.3333),
+ (25,27233.3333,10450.0000),
+ (26,27233.3333,11783.3333),
+ (27,27266.6667,10383.3333),
+ (28,27433.3333,12400.0000),
+ (29,27462.5000,12992.2222);
+```
+
+Next we use the `pgr_TSPeuclidean` function to find the best path.
+
+{/* prettier-ignore */}
+```sql
+select
+ *
+from
+ pgr_TSPeuclidean($$select * from wi29$$)
+```
+
+{/* prettier-ignore */}
+```sql
+ seq | node | cost | agg_cost
+-----+------+------------------+------------------
+ 1 | 1 | 0 | 0
+ 2 | 2 | 74.535614157127 | 74.535614157127
+ 3 | 6 | 900.617093380362 | 975.152707537489
+ 4 | 10 | 2113.77757765045 | 3088.93028518793
+ 5 | 11 | 106.718669615254 | 3195.64895480319
+ 6 | 12 | 1411.95293791574 | 4607.60189271893
+ 7 | 13 | 1314.23824873744 | 5921.84014145637
+ 8 | 14 | 1321.76283931305 | 7243.60298076942
+ 9 | 17 | 1202.91366735569 | 8446.5166481251
+ 10 | 18 | 683.333268292684 | 9129.84991641779
+ 11 | 15 | 1108.05137466134 | 10237.9012910791
+ 12 | 19 | 772.082339448903 | 11009.983630528
+ 13 | 22 | 697.666150054665 | 11707.6497805827
+ 14 | 23 | 220.141999627513 | 11927.7917802102
+ 15 | 21 | 197.926372783442 | 12125.7181529937
+ 16 | 29 | 440.456596290771 | 12566.1747492844
+ 17 | 28 | 592.939989005405 | 13159.1147382898
+ 18 | 26 | 648.288376333318 | 13807.4031146231
+ 19 | 20 | 509.901951359278 | 14317.3050659824
+ 20 | 25 | 1330.83095428717 | 15648.1360202696
+ 21 | 27 | 74.535658878487 | 15722.6716791481
+ 22 | 24 | 559.016994374947 | 16281.688673523
+ 23 | 16 | 1243.87392358622 | 17525.5625971092
+ 24 | 9 | 4088.0585364911 | 21613.6211336004
+ 25 | 7 | 650.85409697993 | 22264.4752305803
+ 26 | 3 | 891.004385199336 | 23155.4796157796
+ 27 | 4 | 1172.36699411442 | 24327.846609894
+ 28 | 8 | 994.708187806297 | 25322.5547977003
+ 29 | 5 | 1188.01888359478 | 26510.5736812951
+ 30 | 1 | 2266.91173136004 | 28777.4854126552
+```
+
+## Resources
+
+- Official [`pgRouting` documentation](https://docs.pgrouting.org/latest/en/index.html)
+
+export const Page = ({ children }) =>
+
+export default Page
diff --git a/apps/docs/pages/guides/database/extensions/pgsodium.mdx b/apps/docs/pages/guides/database/extensions/pgsodium.mdx
new file mode 100644
index 00000000000..f11c5fc3ec8
--- /dev/null
+++ b/apps/docs/pages/guides/database/extensions/pgsodium.mdx
@@ -0,0 +1,49 @@
+import Layout from '~/layouts/DefaultGuideLayout'
+
+export const meta = {
+ id: 'pgsodium',
+ title: 'pgsodium: Encryption Features',
+ description: 'Encryption library for PostgreSQL',
+}
+
+[pgsodium](https://github.com/michelp/pgsodium) is a PostgreSQL extension which provides SQL access to libsodium's high-level cryptographic algorithms. It also enables some Postgres specific features including:
+
+- [Server Key Management](https://github.com/michelp/pgsodium#server-key-management)
+- [Transparent Column Encryption](https://github.com/michelp/pgsodium#transparent-column-encryption)
+
+## Enable the extension
+
+
+
+
+1. Go to the [Database](https://app.supabase.com/project/_/database/tables) page in the Dashboard.
+2. Click on **Extensions** in the sidebar.
+3. Search for "pgsodium" and enable the extension.
+
+
+
+
+{/* prettier-ignore */}
+```sql
+-- Enable the "pgsodium" extension
+create extension pgsodium;
+
+-- Disable the "pgsodium" extension
+drop extension if exists pgsodium;
+```
+
+
+
+
+## Resources
+
+- Official [`pgsodium` documentation](https://github.com/michelp/pgsodium)
+
+export const Page = ({ children }) =>
+
+export default Page
diff --git a/apps/docs/pages/guides/database/extensions/pgtap.mdx b/apps/docs/pages/guides/database/extensions/pgtap.mdx
index fc642b522ab..219eedd11ee 100644
--- a/apps/docs/pages/guides/database/extensions/pgtap.mdx
+++ b/apps/docs/pages/guides/database/extensions/pgtap.mdx
@@ -15,9 +15,7 @@ Let's cover some basic concepts:
- Unit tests: allow you to test small parts of a system (like a database table!).
- TAP: stands for [Test Anything Protocol](http://testanything.org/). It is an framework which aims to simplify the error reporting during testing.
-## Usage
-
-### Enable the extension
+## Enable the extension
-### Testing tables
+## Testing tables
```sql
begin;
@@ -68,7 +66,7 @@ API:
- [`has_index()`](https://pgtap.org/documentation.html#has_index): Checks for the existence of a named index associated with the named table.
- [`has_relation()`](https://pgtap.org/documentation.html#has_relation): Tests whether or not a relation exists in the database.
-### Testing columns
+## Testing columns
```sql
begin;
@@ -86,7 +84,7 @@ API:
- [`has_column()`](https://pgtap.org/documentation.html#has_column): Tests whether or not a column exists in a given table, view, materialized view or composite type.
- [`col_is_pk()`](https://pgtap.org/documentation.html#col_is_pk): Tests whether the specified column or columns in a table is/are the primary key for that table.
-### Testing RLS Policies
+## Testing RLS Policies
```sql
begin;
@@ -133,7 +131,7 @@ API:
- [`results_eq()`](https://pgtap.org/documentation.html#results_eq)
- [`results_ne()`](https://pgtap.org/documentation.html#results_ne)
-### Testing Functions
+## Testing Functions
```sql
begin;
diff --git a/apps/docs/pages/guides/database/extensions/plpgsql_check.mdx b/apps/docs/pages/guides/database/extensions/plpgsql_check.mdx
new file mode 100644
index 00000000000..a064f463df6
--- /dev/null
+++ b/apps/docs/pages/guides/database/extensions/plpgsql_check.mdx
@@ -0,0 +1,105 @@
+import Layout from '~/layouts/DefaultGuideLayout'
+
+export const meta = {
+ id: 'plpgsql_check',
+ title: 'plpgsql_check: PL/pgSQL Linter',
+ description: 'Lint PL/pgSQL code',
+}
+
+[plpgsql_check](https://github.com/okbob/plpgsql_check) is a PostgreSQL extension that lints plpgsql for syntax, semantic and other related issues. The tool helps developers to identify and correct errors before executing the code. plpgsql_check is most useful for developers who are working with large or complex SQL codebases, as it can help identify and resolve issues early in the development cycle.
+
+## Enable the extension
+
+
+
+
+1. Go to the [Database](https://app.supabase.com/project/_/database/tables) page in the Dashboard.
+2. Click on **Extensions** in the sidebar.
+3. Search for "plpgsql_check" and enable the extension.
+
+
+
+
+{/* prettier-ignore */}
+```sql
+-- Enable the "plpgsql_check" extension
+create extension plpgsql_check;
+
+-- Disable the "plpgsql_check" extension
+drop extension if exists plpgsql_check;
+```
+
+Even though the SQL code is `create extension`, this is the equivalent of "enabling the extension".
+To disable an extension you can call `drop extension`.
+
+
+
+
+## API
+
+- [`plpgsql_check_function( ... )`](https://github.com/okbob/plpgsql_check#active-mode): Scans a function for errors.
+
+`plpgsql_check_function` is highly customizable. For a complete list of available arguments see [the docs](https://github.com/okbob/plpgsql_check#arguments)
+
+## Usage
+
+To demonstrate `plpgsql_check` we can create a function with a known error. In this case we create a function `some_func`, that references a non-existent column `place.created_at`.
+
+{/* prettier-ignore */}
+```sql
+create table place(
+ x float,
+ y float
+);
+
+create or replace function public.some_func()
+ returns void
+ language plpgsql
+as $$
+declare
+ rec record;
+begin
+ for rec in select * from place
+ loop
+ -- Bug: There is no column `created_at` on table `place`
+ raise notice '%', rec.created_at;
+ end loop;
+end;
+$$;
+```
+
+Note that executing the function would not catch the invalid reference error because the `loop` does not execute if no rows are present in the table.
+
+{/* prettier-ignore */}
+```sql
+select public.some_func();
+ some_func
+ ───────────
+
+ (1 row)
+```
+
+Now we can use plpgsql_check's `plpgsql_check_function` function to identify the known error.
+
+{/* prettier-ignore */}
+```sql
+select plpgsql_check_function('public.some_func()');
+
+ plpgsql_check_function
+------------------------------------------------------------
+ error:42703:8:RAISE:record "rec" has no field "created_at"
+ Context: SQL expression "rec.created_at"
+```
+
+## Resources
+
+- Official [`plpgsql_check` documentation](https://github.com/okbob/plpgsql_check)
+
+export const Page = ({ children }) =>
+
+export default Page
diff --git a/apps/docs/pages/guides/database/extensions/plv8.mdx b/apps/docs/pages/guides/database/extensions/plv8.mdx
index eb408b2c30f..fdb827f85da 100644
--- a/apps/docs/pages/guides/database/extensions/plv8.mdx
+++ b/apps/docs/pages/guides/database/extensions/plv8.mdx
@@ -15,9 +15,8 @@ While Postgres natively runs SQL, it can also run other "procedural languages".
It can be used for database functions, triggers, queries and more.
-## Usage
-### Enable the extension
+## Enable the extension
-### Create `plv8` functions
+## Create `plv8` functions
Functions written in `plv8` are written just like any other PostgreSQL functions, only
with the `language` identifier set to `plv8`.
@@ -135,6 +134,8 @@ returns setof messages as $$
return json_result;
$$ language plv8;
+
+select * from get_messages();
```
## Resources
diff --git a/apps/docs/pages/guides/database/extensions/postgis.mdx b/apps/docs/pages/guides/database/extensions/postgis.mdx
index 6f241b0774e..9e9a65098ad 100644
--- a/apps/docs/pages/guides/database/extensions/postgis.mdx
+++ b/apps/docs/pages/guides/database/extensions/postgis.mdx
@@ -14,9 +14,7 @@ While you may be able to store simple lat/long geographic coordinates as a set o
The additional data types that PostGIS provides include [Point](https://postgis.net/docs/using_postgis_dbmanagement.html#Point), [Polygon](https://postgis.net/docs/using_postgis_dbmanagement.html#Polygon), [Linestring](https://postgis.net/docs/using_postgis_dbmanagement.html#LineString), and many more to represent different types of geographical data. In this guide, we will mainly focus on how to interact with `Point` type, which represents a single set of latitude and longitude. If you are interested in digging deeper, you can learn more about different data types on the [data management section of PostGIS docs](https://postgis.net/docs/using_postgis_dbmanagement.html).
-## Usage
-
-### Enable the extension
+## Enable the extension
You can get started with PostGIS by enabling the PostGIS extension in your Supabase dashboard.
diff --git a/apps/docs/pages/guides/database/extensions/timescaledb.mdx b/apps/docs/pages/guides/database/extensions/timescaledb.mdx
new file mode 100644
index 00000000000..265e8662ad4
--- /dev/null
+++ b/apps/docs/pages/guides/database/extensions/timescaledb.mdx
@@ -0,0 +1,99 @@
+import Layout from '~/layouts/DefaultGuideLayout'
+
+export const meta = {
+ id: 'timescaledb',
+ title: 'timescaledb: Time-Series data',
+ description: 'Scalable time-series data storage and analysis',
+}
+
+[timescaledb](https://docs.timescale.com/timescaledb/latest/) is a PostgreSQL extension designed for improved handling of time-series data. It provides a scalable, high-performance solution for storing and querying time-series data on top of a standard PostgreSQL database.
+
+`timescaledb` uses a time-series-aware storage model and indexing techniques to improve performance of PostgreSQL in working with time-series data. The extension divides data into chunks based on time intervals, allowing it to scale efficiently, especially for large data sets. The data is then compressed, optimized for write-heavy workloads, and partitioned for parallel processing. `timescaledb` also includes a set of functions, operators, and indexes that work with time-series data to reduce query times, and make data easier to work with.
+
+## Enable the extension
+
+
+
+
+1. Go to the [Database](https://app.supabase.com/project/_/database/tables) page in the Dashboard.
+2. Click on **Extensions** in the sidebar.
+3. Search for "timescaledb" and enable the extension.
+
+
+
+
+{/* prettier-ignore */}
+```sql
+-- Enable the "timescaledb" extension
+create extension timescaledb;
+
+-- Disable the "timescaledb" extension
+drop extension if exists timescaledb;
+```
+
+
+
+
+## Usage
+
+To demonstrate how `timescaledb` works, let's consider a simple example where we have a table that stores temperature data from different sensors. We will create a table named "temperatures" and store data for two sensors.
+
+First we create a hypertable, which is a virtual table that is partitioned into chunks based on time intervals. The hypertable acts as a proxy for the actual table and makes it easy to query and manage time-series data.
+
+{/* prettier-ignore */}
+```sql
+create table temperatures (
+ time timestamp not null,
+ sensor_id int not null,
+ temperature double precision not null
+);
+
+select create_hypertable('temperatures', 'time');
+```
+
+Next, we can populate some values
+
+{/* prettier-ignore */}
+```sql
+insert into temperatures (time, sensor_id, temperature)
+values
+ ('2023-02-14 09:00:00', 1, 23.5),
+ ('2023-02-14 09:00:00', 2, 21.2),
+ ('2023-02-14 09:05:00', 1, 24.5),
+ ('2023-02-14 09:05:00', 2, 22.3),
+ ('2023-02-14 09:10:00', 1, 25.1),
+ ('2023-02-14 09:10:00', 2, 23.9),
+ ('2023-02-14 09:15:00', 1, 24.9),
+ ('2023-02-14 09:15:00', 2, 22.7),
+ ('2023-02-14 09:20:00', 1, 24.7),
+ ('2023-02-14 09:20:00', 2, 23.5);
+```
+
+And finally we can query the table using `timescaledb`'s `time_bucket` function to divide the time-series into intervals of the specified size (in this case, 1 hour) averaging the `temperature` reading within each group.
+
+{/* prettier-ignore */}
+```sql
+select
+ time_bucket('1 hour', time) AS hour,
+ avg(temperature) AS average_temperature
+from
+ temperatures
+where
+ sensor_id = 1
+ and time > NOW() - interval '1 hour'
+group by
+ hour;
+```
+
+## Resources
+
+- Official [`timescaledb` documentation](https://docs.timescale.com/timescaledb/latest/)
+
+export const Page = ({ children }) =>
+
+export default Page
diff --git a/apps/docs/pages/guides/database/extensions/uuid-ossp.mdx b/apps/docs/pages/guides/database/extensions/uuid-ossp.mdx
index 167e9b92807..b270fdd581f 100644
--- a/apps/docs/pages/guides/database/extensions/uuid-ossp.mdx
+++ b/apps/docs/pages/guides/database/extensions/uuid-ossp.mdx
@@ -13,9 +13,7 @@ The `uuid-ossp` extension can be used to generate a `UUID`.
A `UUID` is a "Universally Unique Identifier" and it is, for practical purposes, unique.
This makes them particularly well suited as Primary Keys. It is occasionally referred to as a `GUID`, which stands for "Globally Unique Identifier".
-## Usage
-
-### Enable the extension
+## Enable the extension
-### The `uuid` type
+## The `uuid` type
Once the extension is enabled, you now have access to a `uuid` type.
-### `uuid_generate_v1()`
+## `uuid_generate_v1()`
Creates a UUID value based on the combination of computer’s MAC address, current timestamp, and a random value.
@@ -67,7 +65,7 @@ Creates a UUID value based on the combination of computer’s MAC address, curre
applications.
-### `uuid_generate_v4()`
+## `uuid_generate_v4()`
Creates UUID values based solely on random numbers. You can also use Postgres's built-in [`gen_random_uuid()`](https://www.postgresql.org/docs/current/functions-uuid.html) function to generate a UUIDv4.
diff --git a/apps/docs/pages/guides/database/extensions/wrappers.mdx b/apps/docs/pages/guides/database/extensions/wrappers.mdx
new file mode 100644
index 00000000000..943b1dd6391
--- /dev/null
+++ b/apps/docs/pages/guides/database/extensions/wrappers.mdx
@@ -0,0 +1,75 @@
+import Layout from '~/layouts/DefaultGuideLayout'
+
+export const meta = {
+ id: 'wrappers',
+ title: 'wrappers: 3rd Party Integrations',
+ description: '3rd party integrations for PostgreSQL ',
+}
+
+[supabase/wrappers](https://supabase.github.io/wrappers/) is a PostgreSQL extension that provides integrations with external sources so you can interact with third-party data using SQL.
+
+For example, the [Stripe wrapper](https://supabase.github.io/wrappers/stripe/) connects to [Stripe's API](https://stripe.com/docs/api) and exposes each endpoint as a SQL table.
+
+{/* prettier-ignore */}
+```sql
+select
+ customer_id
+ currency
+from
+ stripe.customers;
+
+ customer_id | currency
+--------------------+-----------
+ cus_MJiBtCqOF1Bb3F | usd
+(1 row)
+```
+
+## Usage
+
+### Enable the extension
+
+
+
+
+1. Go to the [Database](https://app.supabase.com/project/_/database/tables) page in the Dashboard.
+2. Click on **Extensions** in the sidebar.
+3. Search for "wrappers" and enable the extension.
+
+
+
+
+{/* prettier-ignore */}
+```sql
+-- Enable the "wrappers" extension
+create extension wrappers;
+
+-- Disable the "wrappers" extension
+drop extension if exists wrappers;
+```
+
+
+
+
+Instructions for setting up connections to each 3rd party service via SQL are available at the links in the integrations section below.
+A web interface for connecting to external data is coming to Supabase Studio in 2023.
+
+## Integrations
+
+- [Firebase](https://supabase.github.io/wrappers/firebase/)
+- [Stripe](https://supabase.github.io/wrappers/stripe/)
+- Airtable - Coming Soon
+- BigQuery - Coming Soon
+- Clickhouse - Coming Soon
+
+## Resources
+
+- Official [`supabase/wrappers` documentation](https://supabase.github.io/wrappers/)
+
+export const Page = ({ children }) =>
+
+export default Page
diff --git a/apps/docs/pages/guides/database/tables.mdx b/apps/docs/pages/guides/database/tables.mdx
index 13fd0bfa2bd..9ed2619b47d 100644
--- a/apps/docs/pages/guides/database/tables.mdx
+++ b/apps/docs/pages/guides/database/tables.mdx
@@ -242,9 +242,18 @@ You would [connect](../../guides/database/connecting-to-postgres#direct-connecti
```bash
psql -h DATABASE_URL -p 5432 -d postgres -U postgres \
- -c "COPY movies FROM './movies.csv';"
+ -c "\COPY movies FROM './movies.csv';"
```
+Additionally use the `DELIMITER`, `HEADER` and `FORMAT` options as defined in the PostgreSQL [COPY](https://www.postgresql.org/docs/current/sql-copy.html) docs.
+
+```bash
+psql -h DATABASE_URL -p 5432 -d postgres -U postgres \
+ -c "\COPY movies FROM './movies.csv' WITH DELIMITER ',' CSV HEADER"
+```
+
+If you receive an error `FATAL: password authentication failed for user "postgres"`, reset your database password in the Database Settings and try again.
+
## Joining tables with Foreign Keys
Tables can be "joined" together using Foreign Keys.
diff --git a/apps/docs/pages/guides/functions/quickstart.mdx b/apps/docs/pages/guides/functions/quickstart.mdx
index e94aaa84244..f878b495c65 100644
--- a/apps/docs/pages/guides/functions/quickstart.mdx
+++ b/apps/docs/pages/guides/functions/quickstart.mdx
@@ -127,7 +127,7 @@ Edge Functions supports `GET`, `POST`, `PUT`, `PATCH`, `DELETE`, and `OPTIONS`.
- Deno Deploy limitations
- Deno does not support outgoing connections to ports `25`, `465`, and `587`.
- - Cannot write to File System
+ - Cannot read or write to File System
- Edge Functions
- Local development - only one function at a time
- Serving of HTML content is not supported (`GET` requests that return `text/html` will be rewritten to `text/plain`).
diff --git a/apps/docs/pages/guides/getting-started/tutorials/with-sveltekit.mdx b/apps/docs/pages/guides/getting-started/tutorials/with-sveltekit.mdx
index a99cce9b9d9..3be4542731a 100644
--- a/apps/docs/pages/guides/getting-started/tutorials/with-sveltekit.mdx
+++ b/apps/docs/pages/guides/getting-started/tutorials/with-sveltekit.mdx
@@ -55,7 +55,7 @@ import { env } from '$env/dynamic/public'
export const supabase = createClient(env.PUBLIC_SUPABASE_URL, env.PUBLIC_SUPABASE_ANON_KEY)
```
-Optionally, update `src/routes/style.css` with the [CSS from the example](https://raw.githubusercontent.com/supabase/supabase/master/examples/user-management/svelte-user-management/src/app.css).
+Optionally, update `src/routes/styles.css` with the [CSS from the example](https://raw.githubusercontent.com/supabase/supabase/master/examples/user-management/svelte-user-management/src/app.css).
### Supabase Auth Helpers
diff --git a/apps/docs/pages/guides/integrations/flutterflow.mdx b/apps/docs/pages/guides/integrations/flutterflow.mdx
index b7a25f8d1a5..ed6ddf1f046 100644
--- a/apps/docs/pages/guides/integrations/flutterflow.mdx
+++ b/apps/docs/pages/guides/integrations/flutterflow.mdx
@@ -8,8 +8,14 @@ export const meta = {
canonical: 'https://docs.flutterflow.io/actions/actions/backend-database/supabase',
}
+
+ FlutterFlow and Supabase integration is currently in alpha, and supported features may be limited.
+
+
[FlutterFlow](https://flutterflow.io/) is a low-code builder for developing native mobile applications using Flutter. You can use the simple drag-and-drop interface to build your app faster than traditional development.
+This guide gives you a quick overview of implementing basic CRUD operations using FlutterFlow and Supabase. You can find the full docs on FlutterFlow and Supabase [here](https://docs.flutterflow.io/actions/actions/backend-database/supabase).
+
-This guide gives you a quick overview of implementing basic CRUD operations using FlutterFlow and Supabase. You can find the full docs on FlutterFlow and Supabase [here](https://docs.flutterflow.io/actions/actions/backend-database/supabase).
## Step 1: Connect FlutterFlow to Supabase
diff --git a/apps/docs/pages/guides/platform/custom-domains.mdx b/apps/docs/pages/guides/platform/custom-domains.mdx
index 92ea5fd14a3..558496506d1 100644
--- a/apps/docs/pages/guides/platform/custom-domains.mdx
+++ b/apps/docs/pages/guides/platform/custom-domains.mdx
@@ -138,7 +138,7 @@ As such, it is recommended that you schedule a downtime window of 20-30 minutes,
The `activate` subcommand can be used to initiate the activation:
```bash
-supabase vanity-subdomains --project-ref fwmssjhjgszhnavvqxnt check-availability --desired-subdomain my-example-subdomain --experimental
+supabase vanity-subdomains --project-ref fwmssjhjgszhnavvqxnt activate --desired-subdomain my-example-subdomain --experimental
```
## Remove a Vanity Subdomain
diff --git a/apps/docs/pages/guides/platform/database-usage.mdx b/apps/docs/pages/guides/platform/database-usage.mdx
index 8e817bdde0b..6ce6385770d 100644
--- a/apps/docs/pages/guides/platform/database-usage.mdx
+++ b/apps/docs/pages/guides/platform/database-usage.mdx
@@ -6,37 +6,52 @@ export const meta = {
description: 'Understanding how database usage applies to your subscription.',
}
-Database storage refers to the _monthly average storage usage_, as reported by Postgres. This metric is reported in your project's [billing usage](https://app.supabase.com/project/_/settings/billing/usage) and is updated daily.
+Database size refers to the _monthly average storage usage_, as reported by Postgres. This metric is reported in your project's [billing usage](https://app.supabase.com/project/_/settings/billing/usage) and is updated daily.
+Database size is the total size of used storage from your database, whereas disk size describes the size of the underlying available storage.
-For an instantaneous live view of the DB storage usage, you can execute in Postgres:
+For an instantaneous live view of the DB size, you can execute in Postgres:
```sql
-select sum(pg_database_size(pg_database.datname)) / (1024 * 1024) as db_size_mb
-from pg_database;
+select
+ sum(pg_database_size (pg_database.datname)) / (1024 * 1024) as db_size_mb
+from
+ pg_database;
```
This value is also reported in the [database settings page](https://app.supabase.com/project/_/settings/database).
## Database storage management
-Supabase uses network-attached storage to balance performance with scalability. For Pro and Enterprise projects, database storage expands ~1.5x automatically (e.g., 8GB -> 12GB) when you reach 90% of the database storage quota. Automatic database storage expansion can only occur once every six hours. Pro projects can store up to 1024TB.
+Supabase uses network-attached storage to balance performance with scalability.
+For Pro and Enterprise projects, disk size expands ~1.5x automatically (e.g., 8GB -> 12GB) when you reach 90% of the disk size.
+Disk size expansion can only occur once every six hours.
+Pro projects can store up to 1024TB.
-All projects enter read-only mode when you reach 95% of the database storage quota. In read-only mode, clients will encounter errors such as `cannot execute INSERT in a read-only transaction`. Regular operation (read-write mode) is automatically re-enabled once usage is below 95% of the database storage quota.
+All projects enter read-only mode when you reach 95% of the disk size. In read-only mode, clients will encounter errors such as `cannot execute INSERT in a read-only transaction`.
+Regular operation (read-write mode) is automatically re-enabled once usage is below 95% of the disk size.
-If you need more than 1024TB of database storage or require multiple storage expansions in a short period of time, [contact us](https://app.supabase.com/support/new) to learn more about the Enterprise plan.
+If you need more than 1024TB of disk size or require multiple storage expansions in a short period of time, [contact us](https://app.supabase.com/support/new) to learn more about the Enterprise plan.
-### Increasing available database storage
+### Increasing available disk size
-1. [Upgrade to the Pro or Enterprise plan](https://app.supabase.com/project/_/settings/billing/subscription) to increase your storage quota.
+1. [Upgrade to the Pro or Enterprise plan](https://app.supabase.com/project/_/settings/billing/subscription) to increase your quota and expand your disk size automatically.
2. Delete data from your project's database to lower its disk usage. If your database is already in read-only mode, run the following command to change the transaction mode to read-write for your session:
```sql
- SET default_transaction_read_only = 'off';
+ SET
+ default_transaction_read_only = 'off';
```
This allows you to delete data from within the session.
+### Preoccupied Space
+
+When launching a new project, your database size will be roughly ~40-60mb.
+The space is used up by preinstalled extensions, schema/data used by our services that are offered with each project and default Postgres data.
+
+When installing additional extensions, even if you don't actively use them, additional database size is used.
+
## Vacuum operations
Postgres does not immediately reclaim the physical space used by dead tuples (i.e., deleted rows) in the DB. Instead, they are internally marked as removed until a [vacuum operation](https://www.postgresql.org/docs/current/routine-vacuuming.html) is executed.
diff --git a/apps/docs/pages/guides/resources.mdx b/apps/docs/pages/guides/resources.mdx
index c85f5447d3c..c5242d1aef4 100644
--- a/apps/docs/pages/guides/resources.mdx
+++ b/apps/docs/pages/guides/resources.mdx
@@ -135,6 +135,12 @@ export const migrationGuides = [
href: '/guides/resources/migrating-to-supabase/heroku',
description: 'Migrate your Heroku Postgres database to Supabase.',
},
+ {
+ title: 'Render',
+ icon: '/docs/img/icons/render-icon',
+ href: '/guides/resources/migrating-to-supabase/render',
+ description: 'Migrate your Render Postgres database to Supabase.',
+ },
]
export const postgres = [
diff --git a/apps/docs/pages/guides/resources/migrating-to-supabase/render.mdx b/apps/docs/pages/guides/resources/migrating-to-supabase/render.mdx
new file mode 100644
index 00000000000..1e477b0e586
--- /dev/null
+++ b/apps/docs/pages/guides/resources/migrating-to-supabase/render.mdx
@@ -0,0 +1,88 @@
+import Layout from '~/layouts/DefaultGuideLayout'
+
+export const meta = {
+ title: 'Migrate from Render to Supabase',
+ description: 'Migrate your Render Postgres database to Supabase.',
+}
+
+Render is a popular Web Hosting service in the online services category that also has a managed Postgres service. Render has a great developer experience, allowing users to deploy straight from GitHub or GitLab. This is the core of their product and they do it really well. However, when it comes to Postgres databases, it may not be the best option.
+
+Supabase is one of the best free alternative to Render Postgres. Supabase provide all the backend features developers need to build a product: a Postgres database, authentication, instant APIs, edge functions, realtime subscriptions, and storage. Postgres is the core of Supabase—for example, you can use row-level security and there are more than 40 Postgres extensions available.
+
+This guide demonstrates how to migrate from Render to Supabase to get the most out of Postgres while gaining access to all the features you need to build a project.
+
+## Retrieve your Render database credentials [#retrieve-render-credentials]
+
+1. Log in to your [Render account](https://render.com) and select the project you want to migrate.
+1. Click **Dashboard** in the menu and click in your **Postgres** database.
+1. Scroll down in the **Info** tab.
+1. Click on **PSQL Command** and edit it adding the content after `PSQL_COMMAND=`.
+
+
+Example:
+
+```bash
+%env PSQL_COMMAND=PGPASSWORD=RgaMDfTS_password_FTPa7 psql -h dpg-a_server_in.oregon-postgres.render.com -U my_db_pxl0_user my_db_pxl0
+```
+
+## Retrieve your Supabase Host [#retrieve-supabase-host]
+
+1. If you're new to Supabase, [create a project](https://app.supabase.com).
+1. Go to the [Database settings](https://app.supabase.com/project/_/settings/database) for your project in the Supabase Dashboard.
+1. Under **Connection Info**, note your Host (`$SUPABASE_HOST`).
+1. Save your password or [reset it](https://app.supabase.com/project/_/settings/database) if you forgot it.
+
+
+
+## Migrate the database
+
+The fastest way to migrate your database is with the Supabase migration tool on [Google Colab](https://colab.research.google.com/github/mansueli/Supa-Migrate/blob/main/Migrate_Postgres_Supabase.ipynb). Alternatively, you can use the [pg_dump](https://www.postgresql.org/docs/current/app-pgdump.html) and [psql](https://www.postgresql.org/docs/current/app-psql.html) command line tools, which are included in a full PostgreSQL installation.
+
+
+
+
+1. Set the environment variables (`PSQL_COMMAND`, `SUPABASE_HOST`, `SUPABASE_PASSWORD`) in the Colab notebook.
+1. Run the first two steps in [the notebook](https://colab.research.google.com/github/mansueli/Supa-Migrate/blob/main/Migrate_Postgres_Supabase.ipynb) in order. The first sets the variables and the second installs PSQL and the migration script.
+1. Run the third step to start the migration. This will take a few minutes.
+
+
+
+
+### Export your Render database to a file in console [#export-render-database]
+
+Use `pg_dump` with your Render credentials to export your Render database to a file (e.g., `render_dump.sql`).
+
+```bash
+pg_dump --clean --if-exists --quote-all-identifiers \
+ -h $RENDER_HOST -U $RENDER_USER -d $RENDER_DATABASE \
+ --no-owner --no-privileges > render_dump.sql
+```
+
+### Import the database to your Supabase project [#import-database-to-supabase]
+
+Use `psql` to import the Render database file to your Supabase project.
+
+```bash
+psql -h $SUPA_URL -U postgres --file render_dump.sql -p 6543 -d postgres
+```
+
+### Additional options
+
+- To only migrate a single database schema, add the `--schema=PATTERN` parameter to your `pg_dump` command.
+- To exclude a schema: `--exclude-schema=PATTERN`.
+- To only migrate a single table: `--table=PATTERN`.
+- To exclude a table: `--exclude-table=PATTERN`.
+
+Run `pg_dump --help` for a full list of options.
+
+
+
+
+export const Page = ({ children }) =>
+
+export default Page
diff --git a/apps/docs/pages/learn/auth-deep-dive/auth-policies.mdx b/apps/docs/pages/learn/auth-deep-dive/auth-policies.mdx
index 636eef09903..00caf731fbc 100644
--- a/apps/docs/pages/learn/auth-deep-dive/auth-policies.mdx
+++ b/apps/docs/pages/learn/auth-deep-dive/auth-policies.mdx
@@ -155,6 +155,17 @@ create policy "Only Blizzard staff can update leaderboard"
Supabase comes with two built-in helper functions: `auth.uid()` and `auth.jwt()`.
+To create your own functions, navigate to the SQL editor and create a a new query.
+
+```sql
+create or replace function user_agent()
+returns text
+language sql
+as $$
+ select nullif(current_setting('request.headers', true)::json->>'user-agent', '')::text;
+$$;
+```
+
See the full PostgreSQL policy docs here: [https://www.postgresql.org/docs/12/sql-createpolicy.html](https://www.postgresql.org/docs/12/sql-createpolicy.html)
You can get as creative as you like with these policies.
diff --git a/apps/docs/public/humans.txt b/apps/docs/public/humans.txt
index 60e064da4b0..2daf85cdf42 100644
--- a/apps/docs/public/humans.txt
+++ b/apps/docs/public/humans.txt
@@ -13,7 +13,6 @@ Bo Lu
Bobbie Soedirgo
Chase Granberry
Chris Copplestone
-Danny Ng
Dave Wilson
Div Arora
Egor Romanov
diff --git a/apps/docs/public/img/guides/resources/migrating-to-supabase/render/render_dashboard.png b/apps/docs/public/img/guides/resources/migrating-to-supabase/render/render_dashboard.png
new file mode 100644
index 00000000000..1645901dc23
Binary files /dev/null and b/apps/docs/public/img/guides/resources/migrating-to-supabase/render/render_dashboard.png differ
diff --git a/apps/docs/public/img/guides/resources/migrating-to-supabase/render/supabase_dashboard.png b/apps/docs/public/img/guides/resources/migrating-to-supabase/render/supabase_dashboard.png
new file mode 100644
index 00000000000..3a1ff5b9daf
Binary files /dev/null and b/apps/docs/public/img/guides/resources/migrating-to-supabase/render/supabase_dashboard.png differ
diff --git a/apps/docs/public/img/icons/render-icon.svg b/apps/docs/public/img/icons/render-icon.svg
new file mode 100644
index 00000000000..49e41abdba4
--- /dev/null
+++ b/apps/docs/public/img/icons/render-icon.svg
@@ -0,0 +1,13 @@
+
diff --git a/apps/docs/public/img/integrations/logos/flutterflow_logo.png b/apps/docs/public/img/integrations/logos/flutterflow_logo.png
index 189e7ddc37d..c901cacb076 100644
Binary files a/apps/docs/public/img/integrations/logos/flutterflow_logo.png and b/apps/docs/public/img/integrations/logos/flutterflow_logo.png differ
diff --git a/apps/docs/public/img/supabase-oauth-logins.png b/apps/docs/public/img/supabase-oauth-logins.png
index 8b92d40169d..825ae97fb76 100644
Binary files a/apps/docs/public/img/supabase-oauth-logins.png and b/apps/docs/public/img/supabase-oauth-logins.png differ
diff --git a/apps/docs/public/sitemap.xml b/apps/docs/public/sitemap.xml
index 90cd255fc9c..35bf4d7ff55 100644
--- a/apps/docs/public/sitemap.xml
+++ b/apps/docs/public/sitemap.xml
@@ -3867,4 +3867,4 @@
weekly0.5
-
+
\ No newline at end of file
diff --git a/apps/docs/styles/docsearch.scss b/apps/docs/styles/docsearch.scss
index 8d0cb5f6cd6..569965efcd9 100644
--- a/apps/docs/styles/docsearch.scss
+++ b/apps/docs/styles/docsearch.scss
@@ -7,6 +7,7 @@
.DocSearch-Container {
background: rgba(0, 0, 0, 0.3);
@apply fixed top-0 left-0 z-[200] flex h-screen w-screen flex-col p-4 backdrop-blur-sm sm:p-6 md:p-[10vh] lg:p-[12vh];
+ @apply cursor-auto #{!important};
}
.DocSearch-LoadingIndicator svg {
diff --git a/apps/docs/tsconfig.json b/apps/docs/tsconfig.json
index ce8de4c02e1..c87a0e51e07 100644
--- a/apps/docs/tsconfig.json
+++ b/apps/docs/tsconfig.json
@@ -6,7 +6,7 @@
"paths": {
"~/*": ["./*"]
},
- "target": "es5",
+ "target": "es2015",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
diff --git a/apps/www/_blog/2020-05-01-supabase-alpha-april-2020.mdx b/apps/www/_blog/2020-05-01-supabase-alpha-april-2020.mdx
index 4081fb5f644..964cfe71934 100644
--- a/apps/www/_blog/2020-05-01-supabase-alpha-april-2020.mdx
+++ b/apps/www/_blog/2020-05-01-supabase-alpha-april-2020.mdx
@@ -18,15 +18,15 @@ Now in [Supabase](https://app.supabase.com):
- Query your database directly from the dashboard
- Analyze your queries and make them faster :rocket:
-
+
+
+
## Important Notes
diff --git a/apps/www/_blog/2020-06-01-supabase-alpha-may-2020.mdx b/apps/www/_blog/2020-06-01-supabase-alpha-may-2020.mdx
index afacbae0458..fc9d49b9ba6 100644
--- a/apps/www/_blog/2020-06-01-supabase-alpha-may-2020.mdx
+++ b/apps/www/_blog/2020-06-01-supabase-alpha-may-2020.mdx
@@ -14,15 +14,15 @@ video: https://www.youtube.com/v/e4qXmcEFaUs
It has been a monster month at [Supabase](/) and we're back with a video update. It's a longer video this month because we have a couple of housekeeping items.
-
+
+
+
## Housekeeping
diff --git a/apps/www/_blog/2020-07-01-supabase-alpha-june-2020.mdx b/apps/www/_blog/2020-07-01-supabase-alpha-june-2020.mdx
index f84f144d0ea..4baf3f31a58 100644
--- a/apps/www/_blog/2020-07-01-supabase-alpha-june-2020.mdx
+++ b/apps/www/_blog/2020-07-01-supabase-alpha-june-2020.mdx
@@ -17,66 +17,66 @@ We're now 4 months into building [Supabase](/), which means another major update
Watch a full demo:
-
+
+
+
### View relational data
We're sometimes asked how we will make Postgres as simple as Firebase, since Postgres is a relational database. This month we're making our first steps to prove that relational databases can be even easier to use than document stores. We're releasing an excel-like editing interface which can drill down into your relational data.
-
+
+
+
### Manage JSON data
Postgres is an amazing database, giving the flexibility of a document store with the power of a RDBMS. If you use `JSON` data in Postgres, then we want to make that easy too. Supabase detects when your column is `JSON` or `JSONB`, and provides an easy way to edit and view your data. More improvements coming soon for this feature!
-
+
+
+
### Choose your region
If you noticed a bit of latency on Supabase, it's because your projects were previously set up in Singapore. It was always our intention that you'd be able to choose your database region, and this month we've delivered it. In the next releases we'll even allow you to go multi-region, instantly replicating your database close to your customers.
-
+
+
+
### Backups
A guiding principle at Supabase is zero lock-in. So this month we are exposing your daily database backups on the dashboard, giving you a simple way to migrate off Supabase. We have a lot more to build in this space (`WAL-G!`), so watch this space.
-
+
+
+
### Kaizen
diff --git a/apps/www/_blog/2020-08-02-supabase-alpha-july-2020.mdx b/apps/www/_blog/2020-08-02-supabase-alpha-july-2020.mdx
index 6d5847735c1..e93c38d00f3 100644
--- a/apps/www/_blog/2020-08-02-supabase-alpha-july-2020.mdx
+++ b/apps/www/_blog/2020-08-02-supabase-alpha-july-2020.mdx
@@ -17,14 +17,14 @@ After 5 months of building, we're releasing one of our most anticipated features
Watch a full demo:
-
+
+
+
### Auth
diff --git a/apps/www/_blog/2020-09-03-supabase-alpha-august-2020.mdx b/apps/www/_blog/2020-09-03-supabase-alpha-august-2020.mdx
index e20bf8a6a58..1a63a1a4bb9 100644
--- a/apps/www/_blog/2020-09-03-supabase-alpha-august-2020.mdx
+++ b/apps/www/_blog/2020-09-03-supabase-alpha-august-2020.mdx
@@ -17,14 +17,14 @@ We're 6 months into building our hosted database platform and we've made some ma
Watch a full demo:
-
+
+
+
### Easily create tables
diff --git a/apps/www/_blog/2020-10-03-supabase-alpha-september-2020.mdx b/apps/www/_blog/2020-10-03-supabase-alpha-september-2020.mdx
index f30c537eb23..43a2b4bc41a 100644
--- a/apps/www/_blog/2020-10-03-supabase-alpha-september-2020.mdx
+++ b/apps/www/_blog/2020-10-03-supabase-alpha-september-2020.mdx
@@ -18,14 +18,14 @@ We're now 7 months into building Supabase. This update is a big one!
Watch a full demo:
-
+
+
+
### Third-party logins
diff --git a/apps/www/_blog/2020-11-02-supabase-alpha-october-2020.mdx b/apps/www/_blog/2020-11-02-supabase-alpha-october-2020.mdx
index 9e4cc3dc193..158b81d22d5 100644
--- a/apps/www/_blog/2020-11-02-supabase-alpha-october-2020.mdx
+++ b/apps/www/_blog/2020-11-02-supabase-alpha-october-2020.mdx
@@ -19,15 +19,15 @@ We're now 8 months into building Supabase. We're focused on performance, stabili
Watch a full demo:
-
+
+
+
### Supabase.js 1.0
diff --git a/apps/www/_blog/2020-12-01-supabase-alpha-november-2020.mdx b/apps/www/_blog/2020-12-01-supabase-alpha-november-2020.mdx
index 7910f61bc42..a4bb7858a82 100644
--- a/apps/www/_blog/2020-12-01-supabase-alpha-november-2020.mdx
+++ b/apps/www/_blog/2020-12-01-supabase-alpha-november-2020.mdx
@@ -19,15 +19,15 @@ We've been building for 9 months now, and we're getting even closer to Beta.
Watch a full demo:
-
+
+
+
### Add users
diff --git a/apps/www/_blog/2020-12-02-case-study-monitoro.mdx b/apps/www/_blog/2020-12-02-case-study-monitoro.mdx
index 4bfceaf4e42..0c628e8c394 100644
--- a/apps/www/_blog/2020-12-02-case-study-monitoro.mdx
+++ b/apps/www/_blog/2020-12-02-case-study-monitoro.mdx
@@ -46,12 +46,12 @@ Thanks to Supabase, Monitoro could deliver the features required needed faster t
Learn more about Monitoro:
-
+
+
+
diff --git a/apps/www/_blog/2021-01-02-supabase-beta-december-2020.mdx b/apps/www/_blog/2021-01-02-supabase-beta-december-2020.mdx
index 91bcdeb34ee..223849745f9 100644
--- a/apps/www/_blog/2021-01-02-supabase-beta-december-2020.mdx
+++ b/apps/www/_blog/2021-01-02-supabase-beta-december-2020.mdx
@@ -20,15 +20,15 @@ After 10 hectic months of building, Supabase is now in Beta.
Watch a full demo:
-
+
+
+
### Supabase is now in Beta
diff --git a/apps/www/_blog/2021-02-02-supabase-beta-january-2021.mdx b/apps/www/_blog/2021-02-02-supabase-beta-january-2021.mdx
index 10e86ed878e..976c96f08b6 100644
--- a/apps/www/_blog/2021-02-02-supabase-beta-january-2021.mdx
+++ b/apps/www/_blog/2021-02-02-supabase-beta-january-2021.mdx
@@ -20,15 +20,15 @@ New year, new features. We've been busy at Supabase during January and our commu
Watch a full demo:
-
+
+
+
### Count functionality
diff --git a/apps/www/_blog/2021-03-02-supabase-beta-february-2021.mdx b/apps/www/_blog/2021-03-02-supabase-beta-february-2021.mdx
index 2a14b71e615..2fe5da5efef 100644
--- a/apps/www/_blog/2021-03-02-supabase-beta-february-2021.mdx
+++ b/apps/www/_blog/2021-03-02-supabase-beta-february-2021.mdx
@@ -19,15 +19,15 @@ Supabase is an open source Firebase alternative. We've now been building for one
Watch a full demo:
-
+
+
+
### Dashboard Sidebars
diff --git a/apps/www/_blog/2021-03-11-using-supabase-replit.mdx b/apps/www/_blog/2021-03-11-using-supabase-replit.mdx
index e0856ab9753..ead6386295d 100644
--- a/apps/www/_blog/2021-03-11-using-supabase-replit.mdx
+++ b/apps/www/_blog/2021-03-11-using-supabase-replit.mdx
@@ -22,15 +22,15 @@ They support a ton of different languages and execution environments and even re
As a Replit user, if you want to access larger amounts of data direct from your repl, or if you fancy accessing some super-powerful query tools, at some point you may want to start interacting with a relational database. Supabase is a good fit here; just like Replit, you don't need to worry about servers, and hosting, you can just click a few buttons and get a fully featured relational database which you can start communicating with directly from javacript, using supabase-js.
-
+
+
+
Here's how to start a Supabase + Node.js repl:
diff --git a/apps/www/_blog/2021-04-06-supabase-beta-march-2021.mdx b/apps/www/_blog/2021-04-06-supabase-beta-march-2021.mdx
index ba28fa69618..b12b9071c0b 100644
--- a/apps/www/_blog/2021-04-06-supabase-beta-march-2021.mdx
+++ b/apps/www/_blog/2021-04-06-supabase-beta-march-2021.mdx
@@ -19,15 +19,15 @@ Launch week, Storage, Supabase CLI, Connection Pooling, Supabase UI, and Pricing
Watch a full demo:
-
+
+
+
## Supabase Storage
diff --git a/apps/www/_blog/2021-05-03-supabase-beta-april-2021.mdx b/apps/www/_blog/2021-05-03-supabase-beta-april-2021.mdx
index fb40d53ea65..96bde96182e 100644
--- a/apps/www/_blog/2021-05-03-supabase-beta-april-2021.mdx
+++ b/apps/www/_blog/2021-05-03-supabase-beta-april-2021.mdx
@@ -20,15 +20,15 @@ Check out what we were working on below, as well as some incredible Community co
Watch a full demo:
-
+
+
+
## Light Mode
@@ -124,15 +124,15 @@ $$ language plv8;
Fireship reviewed Supabase last week, and despite being a (self-proclaimed) Firebase fan-boy, the review was very impartial.
-
+
+
+
## Community
diff --git a/apps/www/_blog/2021-06-02-supabase-beta-may-2021.mdx b/apps/www/_blog/2021-06-02-supabase-beta-may-2021.mdx
index 6469fa46f1d..7106971fe2f 100644
--- a/apps/www/_blog/2021-06-02-supabase-beta-may-2021.mdx
+++ b/apps/www/_blog/2021-06-02-supabase-beta-may-2021.mdx
@@ -19,15 +19,15 @@ Apple & Twitter Logins, Supabase Grid, Go & Swift Libraries. Lots of great stuff
Watch a full demo:
-
+
+
+
## Apple logins are now available
@@ -88,15 +88,15 @@ We started a weekly 1-hour live stream where we build in public.
- [Build in Public 001](https://www.youtube.com/watch?v=p561ogKZ63o): Building a "Next.js + Supabase" Tutorial
- [Build in Public 002](https://twitter.com/p_phiri/status/1397815806990372865?s=20): `maybeSingle()` and "React + Supabase" Tutorial
-
+
+
+
## Community
diff --git a/apps/www/_blog/2021-07-02-supabase-beta-june-2021.mdx b/apps/www/_blog/2021-07-02-supabase-beta-june-2021.mdx
index d42f3c7bbb6..eb4413eab73 100644
--- a/apps/www/_blog/2021-07-02-supabase-beta-june-2021.mdx
+++ b/apps/www/_blog/2021-07-02-supabase-beta-june-2021.mdx
@@ -20,15 +20,15 @@ Supabase is gearing up for another Launch Week on July the 26th. Until then, her
Watch a full demo of this month's releases
-
+
+
+
## Vercel integration
@@ -113,15 +113,15 @@ We run a weekly 1-hour live stream where we build in public.
- [Build in Public 006](https://youtu.be/LHYrqBb4q9I): Supabase Studio
- [Build in Public 007](https://youtu.be/R4gJhX_JFTo): Supabase Studio - API Docs
-
+
+
+
## Community
diff --git a/apps/www/_blog/2021-07-28-supabase-auth-passwordless-sms-login.mdx b/apps/www/_blog/2021-07-28-supabase-auth-passwordless-sms-login.mdx
index 72e22de1d6a..10728c41e42 100644
--- a/apps/www/_blog/2021-07-28-supabase-auth-passwordless-sms-login.mdx
+++ b/apps/www/_blog/2021-07-28-supabase-auth-passwordless-sms-login.mdx
@@ -55,15 +55,15 @@ Simply plug your Twilio credentials into your Auth Settings in the Supabase Dash
Check out the [documentation](/docs/guides/auth/phone-login/twilio) to get started with Mobile OTPs,
or watch the [Youtube guide](https://youtu.be/akScoPO01bc).
-
+
+
+
### Multi-Factor Auth coming soon
diff --git a/apps/www/_blog/2021-08-12-supabase-beta-july-2021.mdx b/apps/www/_blog/2021-08-12-supabase-beta-july-2021.mdx
index c38bb4d977c..244b30497a8 100644
--- a/apps/www/_blog/2021-08-12-supabase-beta-july-2021.mdx
+++ b/apps/www/_blog/2021-08-12-supabase-beta-july-2021.mdx
@@ -20,15 +20,15 @@ July ended with our second "Launch Week", where we shipped something every day f
Watch a full demo of this month's releases.
-
+
+
+
## Launch Week II: The SQL
diff --git a/apps/www/_blog/2021-09-10-supabase-beta-august-2021.mdx b/apps/www/_blog/2021-09-10-supabase-beta-august-2021.mdx
index 1bc892b92a2..545489aebc5 100644
--- a/apps/www/_blog/2021-09-10-supabase-beta-august-2021.mdx
+++ b/apps/www/_blog/2021-09-10-supabase-beta-august-2021.mdx
@@ -20,15 +20,15 @@ We've raised $30M and shipped a bunch of features. Let's dive into what's been h
Watch a recap of this month's release.
-
+
+
+
## We raised $30 million
@@ -90,15 +90,15 @@ We now provide a handy copy utility for various database connection strings beca
RLS can be a bit foreign for developers getting started with Postgres.
This video by [@\_dijonmusters](https://twitter.com/_dijonmusters) demystifies it. If you find the video a useful medium for learning, consider [subscribing to our channel](https://www.youtube.com/c/supabase).
-
+
+
+
## Community
diff --git a/apps/www/_blog/2021-10-04-supabase-beta-sept-2021.mdx b/apps/www/_blog/2021-10-04-supabase-beta-sept-2021.mdx
index 587aa3d2207..893881ea4c7 100644
--- a/apps/www/_blog/2021-10-04-supabase-beta-sept-2021.mdx
+++ b/apps/www/_blog/2021-10-04-supabase-beta-sept-2021.mdx
@@ -67,15 +67,15 @@ We've revamped the Auth docs - The docs are now broken down into [Authentication
Low Code demo, Using Supabase with [Clutch.io](http://clutch.io/) - [@\_dijonmusters](https://twitter.com/_dijonmusters) ran a [session at General Assembly](https://www.youtube.com/watch?t=642&v=5fsKMTeBKKY) showing how to use these two tools together to create apps using a low code approach.
-
+
+
+
## Community
diff --git a/apps/www/_blog/2021-11-05-supabase-beta-october-2021.mdx b/apps/www/_blog/2021-11-05-supabase-beta-october-2021.mdx
index ca0dc8ca935..b6e75148a87 100644
--- a/apps/www/_blog/2021-11-05-supabase-beta-october-2021.mdx
+++ b/apps/www/_blog/2021-11-05-supabase-beta-october-2021.mdx
@@ -20,15 +20,15 @@ We released three new Auth providers, multi-schema support, and we're gearing up
Watch a recap of this month's release.
-
+
+
+
## Slack, Spotify, and MessageBird logins
@@ -63,43 +63,43 @@ We have a TON of new guides, with videos too.
Learn about PostgreSQL Functions. [Docs](https://supabase.com/docs/guides/database/functions).
-
+
+
+
### Auth Overview
Learn about all the exciting features of Auth within Supabase. [Docs](https://supabase.com/docs/guides/auth).
-
+
+
+
### API Features
Learn more about the power of PostgREST for RESTful APIs. [Docs](https://supabase.com/docs/guides/database/api#restful-api).
-
+
+
+
### And more
@@ -118,29 +118,29 @@ There was a lot of activity this month.
Supabase attended the Jamstack conf. Watch us catch up with Matt, the cofounder of Netlify (minute 8).
-
+
+
+
### Supabase at Next.js conf
And [Jon](https://twitter.com/_dijonmusters) made a guest appearance at this year's amazing [Next.js Conf](https://nextjs.org/conf).
-
+
+
+
### Community Highlights
diff --git a/apps/www/_blog/2021-11-29-community-day-lw3.mdx b/apps/www/_blog/2021-11-29-community-day-lw3.mdx
index 3bebaafceac..eda838497c6 100644
--- a/apps/www/_blog/2021-11-29-community-day-lw3.mdx
+++ b/apps/www/_blog/2021-11-29-community-day-lw3.mdx
@@ -27,15 +27,15 @@ Supabase provides a whole bunch of features that are made up of a [collection of
As a user of our hosted offering you don't really need to know all this, but we love showcasing the amazing open-source tools that we work with, and so we've invited [Vik Gamov](https://twitter.com/gAmUssA) to give you a little intro to [Kong](https://github.com/Kong/kong):
-
+
+
+
## PostgreSQL 14 updates
@@ -118,15 +118,15 @@ Anytime we scroll through [madewithsupabase.com](https://www.madewithsupabase.co
So we reached out to some of our users, requesting a short video to learn more and we were overwhelmed with your gracious submissions. We've started a user stories playlist and we are so excited to see many more of these stories in the future. 💚
-
+
+
+
And with that, we officially declare Launch Week as open 🥳 Check back [here](/blog) every day this week to see what new things we are shipping. We can't wait to share them with you 🚀
diff --git a/apps/www/_blog/2021-12-01-realtime-row-level-security-in-postgresql.mdx b/apps/www/_blog/2021-12-01-realtime-row-level-security-in-postgresql.mdx
index b8a3c3e6e40..cf849781aa6 100644
--- a/apps/www/_blog/2021-12-01-realtime-row-level-security-in-postgresql.mdx
+++ b/apps/www/_blog/2021-12-01-realtime-row-level-security-in-postgresql.mdx
@@ -22,15 +22,15 @@ Today, we're announcing security improvements to Realtime, where database change
## Demo
-
+
+
+
## Overview
diff --git a/apps/www/_blog/2021-12-02-supabase-acquires-logflare.mdx b/apps/www/_blog/2021-12-02-supabase-acquires-logflare.mdx
index 4e3a9e61dea..41b88a7a033 100644
--- a/apps/www/_blog/2021-12-02-supabase-acquires-logflare.mdx
+++ b/apps/www/_blog/2021-12-02-supabase-acquires-logflare.mdx
@@ -106,15 +106,15 @@ In just the past 10 days, we've been working on a few of the new Dashboard featu
Over the next few weeks, we plan to release these features (once we have a chance to polish them up), and you'll be able to query your logs with your favorite language - SQL 😀.
-
+
+
+
## Open source
diff --git a/apps/www/_blog/2022-01-20-supabase-beta-december-2021.mdx b/apps/www/_blog/2022-01-20-supabase-beta-december-2021.mdx
index 11cafb38ef9..39b36792c28 100644
--- a/apps/www/_blog/2022-01-20-supabase-beta-december-2021.mdx
+++ b/apps/www/_blog/2022-01-20-supabase-beta-december-2021.mdx
@@ -38,43 +38,43 @@ Enable it from the Database > Extensions tab in the [Supabase Dashboard](https:/
Jon Meyers walks you through how to write more complex queries and logic in your database, and invoke it via the API.
-
+
+
+
### Call any API using PostgreSQL functions
We recently added the ability to call any external HTTP endpoint from your postgres functions. Jon shows you how!
-
+
+
+
### Create PostgreSQL Functions with Supabase
Triggers allow you to execute SQL in response to any table changes. Learn how to write PostgreSQL triggers with Jon!
-
+
+
+
### Hiring
@@ -90,15 +90,15 @@ There was a lot of activity this month.
Let Jason and Jon show you how to build an app with Next.js and Supabase on top of Netlify!
-
+
+
+
### Divjoy integration
diff --git a/apps/www/_blog/2022-02-22-supabase-beta-january-2022.mdx b/apps/www/_blog/2022-02-22-supabase-beta-january-2022.mdx
index 81c0e8d98b1..a8b72dc03bc 100644
--- a/apps/www/_blog/2022-02-22-supabase-beta-january-2022.mdx
+++ b/apps/www/_blog/2022-02-22-supabase-beta-january-2022.mdx
@@ -109,15 +109,15 @@ There's no stopping [Jon Meyers](https://jonmeyers.io/videos)! He’s back with
You'll learn how to listen to _any_ database change, then send those changes [in a payload](https://supabase.com/blog/supabase-functions-updates#hook-payload) via HTTP request.
-
+
+
+
## Set your own support ticket priority
@@ -141,43 +141,43 @@ Are you maintaining multiple environments? Snaplet helps you copy a production d
Check out the [Supabase clone environments](https://docs.snaplet.dev/tutorials/supabase-clone-environments) tutorial on the Snaplet docs.
-
+
+
+
### Supabase + Retool
Retool has put together a brilliant 10 minute admin panel setup using Supabase with Retool.
-
+
+
+
### Learn with Jason
Jason Lengstorf caught up with our very own [Jon Meyers](https://jonmeyers.io/) on his awesome show, [Learn with Jason](https://www.youtube.com/watch?v=8vqY1KT4TLU), to talk about building an app [with Supabase and NextJS](https://supabase.com/docs/guides/with-nextjs).
-
+
+
+
### New article highlights
diff --git a/apps/www/_blog/2022-03-25-supabase-launch-week-four.mdx b/apps/www/_blog/2022-03-25-supabase-launch-week-four.mdx
index 8edfcd91dde..e0c8dbfed49 100644
--- a/apps/www/_blog/2022-03-25-supabase-launch-week-four.mdx
+++ b/apps/www/_blog/2022-03-25-supabase-launch-week-four.mdx
@@ -35,15 +35,15 @@ to keep updated by following the hashtag [#SupaLaunchWeek](https://twitter.com/h
Watch us as we kick off Launch Week, shipping this blog post.
-
+
+
+
## Monday: Community Day
diff --git a/apps/www/_blog/2022-03-28-community-day-lw4.mdx b/apps/www/_blog/2022-03-28-community-day-lw4.mdx
index 4600e2b03bb..d0245acace2 100644
--- a/apps/www/_blog/2022-03-28-community-day-lw4.mdx
+++ b/apps/www/_blog/2022-03-28-community-day-lw4.mdx
@@ -157,15 +157,15 @@ As always, all of this is open-source. Feedback, feature requests, and contribut
For our community day open source spotlight feature, we’re delighted to have [Bashbunni](https://twitter.com/bash_bunni) give us an overview of what’s possible with Charm:
-
+
+
+
## New courses and learning materials
diff --git a/apps/www/_blog/2022-03-31-supabase-edge-functions.mdx b/apps/www/_blog/2022-03-31-supabase-edge-functions.mdx
index 03eb07a82b7..55d19fb6f92 100644
--- a/apps/www/_blog/2022-03-31-supabase-edge-functions.mdx
+++ b/apps/www/_blog/2022-03-31-supabase-edge-functions.mdx
@@ -108,15 +108,15 @@ To round off the end-to-end experience, `supabase-js` now works in Deno, making
## Quickstart
-
+
+
+
Can't wait to get started? Develop, test, and deploy your first Supabase Edge Function in less than 7 minutes with our [Quickstart Video Tutorial](https://youtu.be/rzglqRdZUQE)!
Check the [Edge Functions Docs](https://supabase.com/docs/guides/functions) for the steps.
diff --git a/apps/www/_blog/2022-04-01-supabase-realtime-with-multiplayer-features.mdx b/apps/www/_blog/2022-04-01-supabase-realtime-with-multiplayer-features.mdx
index 584e9d26dc8..f03ab864253 100644
--- a/apps/www/_blog/2022-04-01-supabase-realtime-with-multiplayer-features.mdx
+++ b/apps/www/_blog/2022-04-01-supabase-realtime-with-multiplayer-features.mdx
@@ -16,15 +16,15 @@ Today is the 1st of April, or April Fool's Day, but I like to think of today as
Here's the next version of Realtime, with multiplayer features.
-
+
+
+
We've made quite a few iterations on Realtime in the past. The first major version of Realtime listened to a single database by streaming changes from PostgreSQL's Write-Ahead Log and sending them to clients via WebSockets. The second major version introduced better security via WALRUS (Write Ahead Log Realtime Unified Security) which adheres to a database's RLS policies to determine authorized clients. However, this version continues to be single tenant as it can only poll one database at a time.
diff --git a/apps/www/_blog/2022-04-20-partner-gallery-works-with-supabase.mdx b/apps/www/_blog/2022-04-20-partner-gallery-works-with-supabase.mdx
index aaf5e4d21b0..b4a0503a236 100644
--- a/apps/www/_blog/2022-04-20-partner-gallery-works-with-supabase.mdx
+++ b/apps/www/_blog/2022-04-20-partner-gallery-works-with-supabase.mdx
@@ -23,15 +23,15 @@ As with anything at Supabase, the Partner Gallery is built on top of the [Supaba
# Partner showcase - new integrations and expert services
-
+
+
+
## Expert services
@@ -43,15 +43,15 @@ Please join us in welcoming our first two experts!
### Morrow
-
+
+
+
Morrow is an app development agency headquartered in Bristol, UK. Their mission is to consult, build and support cutting-edge innovation.
@@ -77,15 +77,15 @@ We’re starting off with a selection across auth, developer tools, and low-code
### Clerk
-
+
+
+
Clerk authenticates users, manages session tokens, and provides user management functionality that can be used in combination with the authorization logic available in Supabase through PostgreSQL Row Level Security (RLS) policies.
@@ -99,15 +99,15 @@ Stytch provides an all-in-one platform for passwordless auth. Stytch makes it ea
### Prisma
-
+
+
+
Prisma is an open source next-generation ORM. It consists of the following parts:
@@ -117,15 +117,15 @@ Prisma is an open source next-generation ORM. It consists of the following parts
### Snaplet
-
+
+
+
Snaplet is a developer tool that copies a Postgres database, transforming personal information, so that you can safely code against actual data. This functionality makes it possible to easily achieve environment parity in Supabase.
@@ -137,15 +137,15 @@ Vercel enables developers to build and publish wonderful things. They build prod
### pgMustard
-
+
+
+
pgMustard speeds up your journey from knowing which query is a problem to working out what can be done about it.
@@ -157,29 +157,29 @@ Even if you know how, Postgres includes so much useful information, it is easy t
### Clutch
-
+
+
+
What can I achieve with Clutch & Supabase? Anything you can think of where a database would be necessary for your project. Examples include building a blog, real-time chat app, booking platform, and todo list, Supabase, and Clutch provides all the necessary tools to build your projects quickly and efficiently.
### Appsmith
-
+
+
+
Appsmith is an open-source framework for building internal tools. It lets you drag-and-drop UI components to build pages, connect to any API, database or GraphQL source and write logic with JavaScript objects.
@@ -191,28 +191,28 @@ Draftbit is a "pro-code" low-code mobile app building platform. Draftbit exports
### Plasmic
-
+
+
+
By connecting Supabase with Plasmic, a visual builder for the web, you can build data-backed applications without code. While many users leverage Plasmic to quickly launch and iterate on landing pages, Plasmic can be used as a general-purpose visual builder for React, which can be used to design and implement fully featured read-write applications.
### Teta
-
+
+
+
Teta is an online cooperative app builder. It allows designers and developers to collaborate in real-time on the design and development of mobile applications, through a convenient visual interface that allows the export of Flutter code.
diff --git a/apps/www/_blog/2022-06-30-flutter-tutorial-building-a-chat-app.mdx b/apps/www/_blog/2022-06-30-flutter-tutorial-building-a-chat-app.mdx
index 8285a717828..cbe86871d1b 100644
--- a/apps/www/_blog/2022-06-30-flutter-tutorial-building-a-chat-app.mdx
+++ b/apps/www/_blog/2022-06-30-flutter-tutorial-building-a-chat-app.mdx
@@ -890,5 +890,6 @@ If you have any questions please reach out via [Twitter](https://twitter.com/dsh
- [Part 2: Flutter Authentication and Authorization with RLS](https://supabase.com/blog/flutter-authentication-and-authorization-with-rls)
- [Flutter Supabase Quick Starter Guide](https://supabase.com/docs/guides/with-flutter)
- [supabase-flutter v1.0 released](https://supabase.com/blog/supabase-flutter-sdk-v1-released)
+- [How to build a real-time multiplayer game with Flutter Flame](https://supabase.com/blog/flutter-real-time-multiplayer-game)
- [Open source map based Flutter app with CI/CD and scalable statemanagement example](https://github.com/dshukertjr/spot)
- [Build a Flutter app with Very Good CLI and Supabase](https://verygood.ventures/blog/flutter-app-very-good-cli-supabase)
diff --git a/apps/www/_blog/2022-07-13-supabase-auth-helpers-with-sveltekit-support.mdx b/apps/www/_blog/2022-07-13-supabase-auth-helpers-with-sveltekit-support.mdx
index 8fcd5b9ff1d..95162e6418e 100644
--- a/apps/www/_blog/2022-07-13-supabase-auth-helpers-with-sveltekit-support.mdx
+++ b/apps/www/_blog/2022-07-13-supabase-auth-helpers-with-sveltekit-support.mdx
@@ -46,15 +46,15 @@ Having good documentation is great, especially when there are [examples](https:/
Remix Auth Helpers are in the works. You can follow the development of this on our [GitHub repo](https://github.com/supabase-community/auth-helpers), or subscribe to our Supabase YouTube channel as we build it live on air.
-
+
+
+
## Contribute
diff --git a/apps/www/_blog/2022-08-02-supabase-flutter-sdk-1-developer-preview.mdx b/apps/www/_blog/2022-08-02-supabase-flutter-sdk-1-developer-preview.mdx
index 39f8595377c..cdabad21e70 100644
--- a/apps/www/_blog/2022-08-02-supabase-flutter-sdk-1-developer-preview.mdx
+++ b/apps/www/_blog/2022-08-02-supabase-flutter-sdk-1-developer-preview.mdx
@@ -160,4 +160,5 @@ If you have any feedbacks, please let us know in the issues of the [supabase-flu
- [supabase-flutter 1.0 developer preview](https://pub.dev/packages/supabase_flutter)
- [Flutter Tutorial: building a Flutter chat app](https://supabase.com/blog/flutter-tutorial-building-a-chat-app)
- [Flutter Tutorial - Part 2: Authentication and Authorization with RLS](https://supabase.com/blog/flutter-authentication-and-authorization-with-rls)
+- [How to build a real-time multiplayer game with Flutter Flame](https://supabase.com/blog/flutter-real-time-multiplayer-game)
- [Build a Flutter app with Very Good CLI and Supabase](https://verygood.ventures/blog/flutter-app-very-good-cli-supabase)
diff --git a/apps/www/_blog/2022-08-10-launch-week-5-hackathon.mdx b/apps/www/_blog/2022-08-10-launch-week-5-hackathon.mdx
index dd12a99bee8..8146287897b 100644
--- a/apps/www/_blog/2022-08-10-launch-week-5-hackathon.mdx
+++ b/apps/www/_blog/2022-08-10-launch-week-5-hackathon.mdx
@@ -24,15 +24,15 @@ There's a $1500 GitHub sponsorship and [gold Supabase caps](https://store.supaba
You can access the supabase-js pre-release, including the new interface by running `npm install @supabase/supabase-js@rc`.
-
+
+
+
## Key Facts
diff --git a/apps/www/_blog/2022-08-12-supabase-series-b.mdx b/apps/www/_blog/2022-08-12-supabase-series-b.mdx
index 0e6c33cc749..5a38458ec7a 100644
--- a/apps/www/_blog/2022-08-12-supabase-series-b.mdx
+++ b/apps/www/_blog/2022-08-12-supabase-series-b.mdx
@@ -113,15 +113,15 @@ pgsodium: [2.0](https://www.postgresql.org/about/news/pgsodium-200-modern-crypto
and [3.0](https://github.com/michelp/pgsodium/releases/tag/v3.0.0) to
support [Transparent Column Encryption](https://github.com/michelp/pgsodium#transparent-column-encryption).
-
+
+
+
diff --git a/apps/www/_blog/2022-08-15-supabase-cli-v1-and-admin-api-beta.mdx b/apps/www/_blog/2022-08-15-supabase-cli-v1-and-admin-api-beta.mdx
index 5bc9888e6ee..f316a907770 100644
--- a/apps/www/_blog/2022-08-15-supabase-cli-v1-and-admin-api-beta.mdx
+++ b/apps/www/_blog/2022-08-15-supabase-cli-v1-and-admin-api-beta.mdx
@@ -17,16 +17,16 @@ In addition, we are releasing a Management API (in beta). The Management API is
(Note: The Management API was previously called the Admin API.)
-
+
+
+
Watch the CLI team announcing the new features.
diff --git a/apps/www/_blog/2022-08-16-supabase-js-v2.mdx b/apps/www/_blog/2022-08-16-supabase-js-v2.mdx
index 5ca0414db64..6708ecaaf54 100644
--- a/apps/www/_blog/2022-08-16-supabase-js-v2.mdx
+++ b/apps/www/_blog/2022-08-16-supabase-js-v2.mdx
@@ -316,16 +316,16 @@ We'll continuing merging security fixes to v1, with maintenance patches for the
## Announcement video and discussion
-
+
+
+
## supabase-js v2 resources
diff --git a/apps/www/_blog/2022-08-17-supabase-soc2.mdx b/apps/www/_blog/2022-08-17-supabase-soc2.mdx
index 7febf307779..c3f4fa8ff6a 100644
--- a/apps/www/_blog/2022-08-17-supabase-soc2.mdx
+++ b/apps/www/_blog/2022-08-17-supabase-soc2.mdx
@@ -92,16 +92,16 @@ Getting the Type 1 certification is just the start, and we will be working on ge
## Announcement video and discussion
-
+
+
+
## More Launch Week 5
diff --git a/apps/www/_blog/2022-08-18-supabase-realtime-multiplayer-general-availability.mdx b/apps/www/_blog/2022-08-18-supabase-realtime-multiplayer-general-availability.mdx
index a30b10b8d00..78da8824758 100644
--- a/apps/www/_blog/2022-08-18-supabase-realtime-multiplayer-general-availability.mdx
+++ b/apps/www/_blog/2022-08-18-supabase-realtime-multiplayer-general-availability.mdx
@@ -224,16 +224,16 @@ We also want to thank [José Valim](https://twitter.com/josevalim), and everyone
## Announcement video and discussion
-
+
+
+
## More Launch Week 5
diff --git a/apps/www/_blog/2022-08-19-launch-week-5-community-day.mdx b/apps/www/_blog/2022-08-19-launch-week-5-community-day.mdx
index dda88680ffd..9f22394c4a7 100644
--- a/apps/www/_blog/2022-08-19-launch-week-5-community-day.mdx
+++ b/apps/www/_blog/2022-08-19-launch-week-5-community-day.mdx
@@ -46,16 +46,16 @@ _Our limited edition swag is approved by professional swag testers like Peanut!_
### Launch Week Landing Page & Golden Tickets
-
+
+
+
We now have a shiny new [Launch Week page](https://supabase.com/launch-week), your one-stop shop for all things Launch Week, where you can find each day's feature as well as highlights from the past Launch Weeks.
@@ -69,16 +69,16 @@ We've added a Golden Ticket mechanic, distributing some special edition launch w
### pg_jsonschema
-
+
+
+
For our Open Source Spotlight we're hearing from [Dmitry Dygalo](https://twitter.com/Stranger6667) from [schemathesis.io](https://schemathesis.io/) who works on a multitude of open-source projects.
He's the author and maintainer of [jsonschema-rs](https://github.com/Stranger6667/jsonschema-rs),
@@ -122,16 +122,16 @@ All that in only 28 mins — or 14 mins on 2x speed!
### Realtime Trello Board with Angular by Simon Grimm
-
+
+
+
[Simon Grimm](https://twitter.com/schlimmson) from [ionicacademy.com](http://ionicacademy.com/) & [devdactic.com](http://devdactic.com/) is releasing a new course for all the Angular fans out there!
@@ -146,16 +146,16 @@ Since releasing our [Partners](/partners) page last Launch Week, we've had a lot
### CodeSandbox Projects Beta
-
+
+
+
The new [CodeSandbox Projects Beta](https://projects.codesandbox.io/) let's you run your projects and turn them into a link you can share with anyone. Developers visiting your link can not only see your running code, they can click “fork” and get an exact copy of that environment so they can easily contribute back to your masterpiece. CodeSandbox integrates directly with GitHub, so you get a unique development environment for every PR you and your contributors open!
@@ -163,31 +163,31 @@ You can do all of this from a web browser, from [within VSCode](https://marketpl
### Dashibase
-
+
+
+
[Dashibase](https://dashibase.com/) is a no-code internal tools builder with a Notion-like UI. With the latest update, you can easily build CRUD dashboards on top of your Supabase projects, set read/write access on your data sources, and share your dashboards with your team. Stop building dashboards from scratch.
### Replicache
-
+
+
+
Replicache is a JavaScript framework for building high performance, collaborative, offline-first web apps.
@@ -197,16 +197,16 @@ Replicache works with Supabase for storage, and Supabase Realtime for its [poke
### [Zapp.run](http://Zapp.run) by Invertase
-
+
+
+
Zapp is a free online sandbox environment for building Flutter applications in the browser. With Zapp, you can enjoy building Flutter apps as though in your favorite IDE, especially if you are a VSCode user; you should feel at home.
@@ -214,16 +214,16 @@ You can install any pub packages and start developing, of course including Supab
### Feelantera
-
+
+
+
Feelantera is a creative digital agency based in Indonesia. They craft web applications with cutting-edge technology, and are one of our new [Supabase Experts](https://supabase.com/partners/experts). They [recently open-sourced](https://twitter.com/feelantera/status/1556909234478059521) their Supabase Edge Functions powered URL shortener on [GitHub](https://github.com/feelantera/fltr-link).
@@ -233,16 +233,16 @@ Are you also experienced in building with Supabase and interested in helping oth
### Snappify
-
+
+
+
snappify is a powerful design tool that helps you to explain and share your code snippets in many beautiful ways.
@@ -260,16 +260,16 @@ To explore the infrastructure backing the project, check out the repository [her
## Hackathon
-
+
+
+
We hope all of these awesome projects and stories gave you plenty of inspiration and you're ready to build in a weekend and scale to millions, as our [hackathon is still running until Sunday](https://supabase.com/blog/launch-week-5-hackathon) night PT!
diff --git a/apps/www/_blog/2022-08-19-pg-jsonschema-a-postgres-extension-for-json-validation.mdx b/apps/www/_blog/2022-08-19-pg-jsonschema-a-postgres-extension-for-json-validation.mdx
index 52f14bd8c01..296165cc10d 100644
--- a/apps/www/_blog/2022-08-19-pg-jsonschema-a-postgres-extension-for-json-validation.mdx
+++ b/apps/www/_blog/2022-08-19-pg-jsonschema-a-postgres-extension-for-json-validation.mdx
@@ -6,6 +6,7 @@ image: lw5-pg_jsonschema/jsonschema-thumb.jpg
thumb: lw5-pg_jsonschema/jsonschema-thumb.jpg
tags:
- launch-week
+ - planetpg
date: '2022-08-19'
toc_depth: 3
---
diff --git a/apps/www/_blog/2022-08-24-building-a-realtime-trello-board-with-supabase-and-angular.mdx b/apps/www/_blog/2022-08-24-building-a-realtime-trello-board-with-supabase-and-angular.mdx
index c0e20a82629..92ccd3f4ee0 100644
--- a/apps/www/_blog/2022-08-24-building-a-realtime-trello-board-with-supabase-and-angular.mdx
+++ b/apps/www/_blog/2022-08-24-building-a-realtime-trello-board-with-supabase-and-angular.mdx
@@ -27,15 +27,16 @@ Along our jurney we will:
- implement **magic link sign in** and user authentication with Angular
- make use of **the realtime capabilities**!
- Watch the video version of the tutorial.
+
+
+
+Watch the video version of the tutorial.
Since there are quite some code snippets we need I've put together the [full source code on Github](https://github.com/saimon24/supabase-trello-angular) so you can easily run the project yourself!
diff --git a/apps/www/_blog/2022-10-21-supabase-flutter-sdk-v1-released.mdx b/apps/www/_blog/2022-10-21-supabase-flutter-sdk-v1-released.mdx
index 92aed5cbb3b..8709747976a 100644
--- a/apps/www/_blog/2022-10-21-supabase-flutter-sdk-v1-released.mdx
+++ b/apps/www/_blog/2022-10-21-supabase-flutter-sdk-v1-released.mdx
@@ -100,4 +100,5 @@ It required massive support from the community to bring the supabase-flutter to
- [v0 to v1 migration guide](https://supabase.com/docs/reference/dart/v0/upgrade-guide)
- [Flutter Tutorial: building a Flutter chat app](https://supabase.com/blog/flutter-tutorial-building-a-chat-app)
- [Flutter Tutorial - Part 2: Authentication and Authorization with RLS](https://supabase.com/blog/flutter-authentication-and-authorization-with-rls)
+- [How to build a real-time multiplayer game with Flutter Flame](https://supabase.com/blog/flutter-real-time-multiplayer-game)
- [Build a Flutter app with Very Good CLI and Supabase](https://verygood.ventures/blog/flutter-app-very-good-cli-supabase)
diff --git a/apps/www/_blog/2022-11-22-flutter-authentication-and-authorization-with-rls.mdx b/apps/www/_blog/2022-11-22-flutter-authentication-and-authorization-with-rls.mdx
index e6ecfbe4cd2..fde39a288d0 100644
--- a/apps/www/_blog/2022-11-22-flutter-authentication-and-authorization-with-rls.mdx
+++ b/apps/www/_blog/2022-11-22-flutter-authentication-and-authorization-with-rls.mdx
@@ -1357,6 +1357,7 @@ We can for example update this to a list of users that are online at the same ti
- [Complete set of code of this chat app](https://github.com/supabase-community/flutter-chat/tree/with_auth)
- [supabase-flutter docs](https://supabase.com/docs/reference/dart)
+- [How to build a real-time multiplayer game with Flutter Flame](https://supabase.com/blog/flutter-real-time-multiplayer-game)
- [Flutter Supabase Quick Starter Guide](https://supabase.com/docs/guides/with-flutter)
- [supabase-flutter v1.0 released](https://supabase.com/blog/supabase-flutter-sdk-v1-released)
- [Build a Flutter app with Very Good CLI and Supabase](https://verygood.ventures/blog/flutter-app-very-good-cli-supabase)
diff --git a/apps/www/_blog/2022-12-12-new-supabase-docs-built-with-nextjs.mdx b/apps/www/_blog/2022-12-12-new-supabase-docs-built-with-nextjs.mdx
index 78bae4b10ed..92688641ed8 100644
--- a/apps/www/_blog/2022-12-12-new-supabase-docs-built-with-nextjs.mdx
+++ b/apps/www/_blog/2022-12-12-new-supabase-docs-built-with-nextjs.mdx
@@ -6,6 +6,7 @@ image: lw6-day-1/lw6-day1-docs.jpg
thumb: lw6-day-1/lw6-day1-docs.jpg
tags:
- launch-week
+ - docs
date: '2022-12-12'
toc_depth: 3
video: https://www.youtube.com/v/Q1Amk6iDlF8
diff --git a/apps/www/_blog/2022-12-14-mfa-auth-via-rls.mdx b/apps/www/_blog/2022-12-14-mfa-auth-via-rls.mdx
index 70e89a15e82..eff5501b53a 100644
--- a/apps/www/_blog/2022-12-14-mfa-auth-via-rls.mdx
+++ b/apps/www/_blog/2022-12-14-mfa-auth-via-rls.mdx
@@ -17,8 +17,10 @@ Today, we’re releasing Multi Factor Authentication for everyone.
Additionally, in preparation for releasing SAML, we're "dogfooding" the feature with the introduction of Single Sign On (SSO) on our dashboard. Contact us at [growth@supabase.com](mailto:growth@supabase.com) if you want to [enable this on your Enterprise plan](https://supabase.com/docs/guides/platform/sso).
-
+
+
+
## What is MFA?
diff --git a/apps/www/_blog/2022-12-15-postgres-foreign-data-wrappers-rust.mdx b/apps/www/_blog/2022-12-15-postgres-foreign-data-wrappers-rust.mdx
index 28beba1875d..fa8bd3a668e 100644
--- a/apps/www/_blog/2022-12-15-postgres-foreign-data-wrappers-rust.mdx
+++ b/apps/www/_blog/2022-12-15-postgres-foreign-data-wrappers-rust.mdx
@@ -6,6 +6,7 @@ image: launch-week-6/wrappers/wrappers-og.png
thumb: launch-week-6/wrappers/wrappers-og.png
tags:
- launch-week
+ - planetpg
date: '2022-12-15'
toc_depth: 3
video: https://www.youtube.com/v/QA2qC5F-4OU
@@ -245,15 +246,15 @@ If you're interested in getting involved or building your own Wrapper, don't hes
- View the documentation: [supabase.github.io/wrappers](https://supabase.github.io/wrappers/)
- Supabase Launch Week: [supabase.com/launch-week](https://supabase.com/launch-week)
-
+
+
+
## More Launch Week 6
diff --git a/apps/www/_blog/2022-12-16-launch-week-6-community-day.mdx b/apps/www/_blog/2022-12-16-launch-week-6-community-day.mdx
index 78facd9830a..ed8855ba1e2 100644
--- a/apps/www/_blog/2022-12-16-launch-week-6-community-day.mdx
+++ b/apps/www/_blog/2022-12-16-launch-week-6-community-day.mdx
@@ -20,16 +20,16 @@ While we've been working hard on new features, the community has been developing
### FlutterFlow
-
+
+
+
[FlutterFlow](https://flutterflow.io/) is a low-code builder for developing native mobile applications using Flutter. You can use the simple drag-and-drop interface to build your app faster than traditional development.
@@ -41,16 +41,16 @@ Watch John from the FlutterFlow team demonstrate how you can [get started with F
### OneSignal
-
+
+
+
[OneSignal](https://onesignal.com/) is a messaging platform allowing you to deliver push notifications, in-app messages, SMS, and emails to your users.
@@ -62,16 +62,16 @@ In the video above, William from OneSignal will show you how you can easily inte
### NextAuth
-
+
+
+
[NextAuth.js](https://next-auth.js.org/) is an open source authentication solution for [Next.js](https://nextjs.org/). It allows you to bring your own Database so that you own your user data.
diff --git a/apps/www/_blog/2022-12-16-launch-week-6-wrap-up.mdx b/apps/www/_blog/2022-12-16-launch-week-6-wrap-up.mdx
index a2a6c96a94d..0e42f6dcdf8 100644
--- a/apps/www/_blog/2022-12-16-launch-week-6-wrap-up.mdx
+++ b/apps/www/_blog/2022-12-16-launch-week-6-wrap-up.mdx
@@ -157,15 +157,15 @@ We've released a new multilingual Full Text Search extension, PGroonga. It's fas
## Flutterflow for Flutter App Development
-
+
+
+
@@ -178,15 +178,15 @@ We partnered with the team at [Flutterflow](https://flutterflow.io/) to bring yo
## OneSignal for Push Notifications
-
+
+
+
diff --git a/apps/www/_blog/2022-12-16-vault-now-in-beta.mdx b/apps/www/_blog/2022-12-16-vault-now-in-beta.mdx
index 2b02cda5cb3..aafb2bfa88f 100644
--- a/apps/www/_blog/2022-12-16-vault-now-in-beta.mdx
+++ b/apps/www/_blog/2022-12-16-vault-now-in-beta.mdx
@@ -58,15 +58,15 @@ Decrypted data can be accessed by a special view that is automatically created w
## Deep Dive on How The Vault works
-
+
+
+
As we mentioned, the Vault uses pgsodium's Transparent Column Encryption (TCE) to store secrets in an authenticated encrypted form. There are some details around that you may be curious about, what does authenticated mean, and where are encryption keys store? This section explains those details.
diff --git a/apps/www/_blog/2023-02-03-openai-embeddings-postgres-vector.mdx b/apps/www/_blog/2023-02-03-openai-embeddings-postgres-vector.mdx
index 264b04033f6..e6c7aa2fe4f 100644
--- a/apps/www/_blog/2023-02-03-openai-embeddings-postgres-vector.mdx
+++ b/apps/www/_blog/2023-02-03-openai-embeddings-postgres-vector.mdx
@@ -6,7 +6,7 @@ image: embeddings/og_pgvector.png
thumb: embeddings/og_pgvector.png
tags:
- postgres
- - ai
+ - AI
date: '2023-02-06'
toc_depth: 3
---
@@ -119,9 +119,9 @@ begin
select
id,
content,
- (documents.embedding <=> query_embedding) as similarity
+ 1 - (documents.embedding <=> query_embedding) as similarity
from documents
- where (documents.embedding <=> query_embedding) > similarity_threshold
+ where 1 - (documents.embedding <=> query_embedding) > similarity_threshold
order by documents.embedding <=> query_embedding
limit match_count;
end;
diff --git a/apps/www/_blog/2023-02-07-chatgpt-supabase-docs.mdx b/apps/www/_blog/2023-02-07-chatgpt-supabase-docs.mdx
index 6c1b29a9e70..0773f7c9713 100644
--- a/apps/www/_blog/2023-02-07-chatgpt-supabase-docs.mdx
+++ b/apps/www/_blog/2023-02-07-chatgpt-supabase-docs.mdx
@@ -5,7 +5,7 @@ author: paul_copplestone
image: docsgpt/og_supabase_clippy.png
thumb: docsgpt/og_supabase_clippy.png
tags:
- - ai
+ - AI
- docs
date: '2023-02-07'
toc_depth: 3
@@ -55,13 +55,13 @@ It's built with Supabase/Postgres, and consists of several key parts:
All the changes we made are in [this](https://github.com/supabase/supabase/pull/12056) Pull Request. Greg is here to explain every step in full detail:
-
+
You can find a full write up in our previous blog post: [Storing OpenAI embeddings in Postgres with pgvector](https://supabase.com/blog/openai-embeddings-postgres-vector).
@@ -90,13 +90,13 @@ A few weeks ago I received an email, drawing our attention to a [Pull Request](h
After we merged the PR, I reached out to see if Greg was interested in helping with some documentation or a blog post to help the community to use pgvector. On the call, Greg surprised me with an initial prototype where he had ingested the Supabase docs for a ChatGPT-like experience. I was suitably impressed and suggested that we could explore ways to work together to build it into the Supabase docs. He wasted no time:
-
+
### A friendly collaboration
diff --git a/apps/www/_blog/2023-02-14-flutter-real-time-multiplayer-game.mdx b/apps/www/_blog/2023-02-14-flutter-real-time-multiplayer-game.mdx
new file mode 100644
index 00000000000..796f60340f8
--- /dev/null
+++ b/apps/www/_blog/2023-02-14-flutter-real-time-multiplayer-game.mdx
@@ -0,0 +1,840 @@
+---
+title: How to build a real-time multiplayer game with Flutter Flame
+description: Build a real-time multiplayer game using Flutter, Flame, and Supabase realtime.
+author: tyler_shukert
+image: flutter-realtime-game/flutter-flame-realtime-multiplayer-game.png
+thumb: flutter-realtime-game/flutter-flame-realtime-multiplayer-game.png
+tags:
+ - flutter
+ - mobile
+ - realtime
+date: '2023-02-14'
+toc_depth: 3
+---
+
+Flutter is a UI library to build apps that run on any platform, but it can also build interactive games thanks to an open-source game engine built on top of Flutter called [Flame](https://flame-engine.org/). Flame takes care of things like collision detection or loading image sprites to bring game development to all the Flutter devs. We can take it a step further to introduce real-time communication features so that players can play against each other in real-time.
+
+In this article, we will use Flutter, Flame, and Supabase's real-time features to build a real-time multiplayer shooting game. You can find the complete code of this tutorial [here](https://github.com/supabase/supabase/tree/master/examples/realtime/flutter-multiplayer-shooting-game).
+
+## Overview of the final game
+
+
+
+The game is a simple shooting game. Each player has a UFO, and you can move it by dragging your finger across the screen. The UFO will emit bullets automatically in three directions, and the objective of the game is to hit the opponents with bullets before your UFO gets destroyed by the opponent’s bullets. The position and the health points are synced using a low-latency web socket connection provided by Supabase.
+
+Before entering the main game, there is a lobby where you can wait for other players to show up. Once another player shows up, you can hit start, which will kick off the game on both ends.
+
+We will first build the Flutter widgets used to build the basic UI, then build the Flame game and finally handle the network connection to share the data between connected clients.
+
+## Build the App
+
+### Step 1. Create the Flutter App
+
+We will start out by creating the Flutter app. Open your terminal and create a new app named with the following command.
+
+```bash
+flutter create flame_realtime_shooting
+```
+
+Open the created app with your favorite IDE and let’s get started with coding!
+
+### Step 2. Building the Flutter widgets
+
+We will have a simple directory structure to build this app. Since we only have a few widgets, we will just add them inside `main.dart` file.
+
+```
+├── lib/
+| ├── game/
+│ │ ├── game.dart
+│ │ ├── player.dart
+│ │ └── bullet.dart
+│ └── main.dart
+```
+
+### Create the Main Game Page
+
+We will create minimal Flutter widgets for this app as most of the game logic will be handled in the Flame Game classes later. Our game will have a single page with two dialogs before the game starts and after the game ends. The page will simply contain the [GameWidget](https://docs.flame-engine.org/1.6.0/flame/game_widget.html) while displaying a nice background image. We will make it a StatefulWidget, because we will add methods to handle sending and receiving real-time data later on. Add the following to `main.dart` file.
+
+```dart
+import 'package:flame/game.dart';
+import 'package:flame_realtime_shooting/game/game.dart';
+import 'package:flutter/material.dart';
+
+void main() {
+ runApp(const MyApp());
+}
+
+class MyApp extends StatelessWidget {
+ const MyApp({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return const MaterialApp(
+ title: 'UFO Shooting Game',
+ debugShowCheckedModeBanner: false,
+ home: GamePage(),
+ );
+ }
+}
+
+class GamePage extends StatefulWidget {
+ const GamePage({Key? key}) : super(key: key);
+
+ @override
+ State createState() => _GamePageState();
+}
+
+class _GamePageState extends State {
+ late final MyGame _game;
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ body: Stack(
+ fit: StackFit.expand,
+ children: [
+ Image.asset('assets/images/background.jpg', fit: BoxFit.cover),
+ GameWidget(game: _game),
+ ],
+ ),
+ );
+ }
+
+ @override
+ void initState() {
+ super.initState();
+ _initialize();
+ }
+
+ Future _initialize() async {
+ _game = MyGame(
+ onGameStateUpdate: (position, health) async {
+ // TODO: handle gmae state update here
+ },
+ onGameOver: (playerWon) async {
+ // TODO: handle when the game is over here
+ },
+ );
+
+ // await for a frame so that the widget mounts
+ await Future.delayed(Duration.zero);
+
+ if (mounted) {
+ _openLobbyDialog();
+ }
+ }
+
+ void _openLobbyDialog() {
+ showDialog(
+ context: context,
+ barrierDismissible: false,
+ builder: (context) {
+ return _LobbyDialog(
+ onGameStarted: (gameId) async {
+ // handle game start here
+ },
+ );
+ });
+ }
+}
+```
+
+You will see some errors, because we are importing some files that we haven’t created yet, but don’t worry, because we will get to it soon.
+
+### Create the Lobby Dialog
+
+
+
+Lobby dialog class on the surface is a simple Alert dialog, but will hold its own states like the list of players that are waiting at the lobby. We will also add some classes to handle the presence data on the lobby later on, but for now we will just have a simple AlertDialog. Add the following code at the end of main.dart file.
+
+```dart
+class _LobbyDialog extends StatefulWidget {
+ const _LobbyDialog({
+ required this.onGameStarted,
+ });
+
+ final void Function(String gameId) onGameStarted;
+
+ @override
+ State<_LobbyDialog> createState() => _LobbyDialogState();
+}
+
+class _LobbyDialogState extends State<_LobbyDialog> {
+ final List _userids = [];
+ bool _loading = false;
+
+ /// TODO: assign unique identifier for the user
+ final myUserId = '';
+
+ @override
+ Widget build(BuildContext context) {
+ return AlertDialog(
+ title: const Text('Lobby'),
+ content: _loading
+ ? const SizedBox(
+ height: 100,
+ child: Center(child: CircularProgressIndicator()),
+ )
+ : Text('${_userids.length} users waiting'),
+ actions: [
+ TextButton(
+ onPressed: _userids.length < 2
+ ? null
+ : () async {
+ setState(() {
+ _loading = true;
+ });
+
+ // TODO: notify the other player the start of the game
+ },
+ child: const Text('start'),
+ ),
+ ],
+ );
+ }
+}
+```
+
+### Step 3. Building the Flame components
+
+### Creating the FlameGame
+
+Now the fun part starts! We will start out by creating our game class. We create a `MyGame` class that extends the [FlameGame](https://docs.flame-engine.org/1.6.0/flame/game.html) class. `FlameGame` takes care of collision detection and pan-detection, and it will also be the parent of all the components that we will add to the game. The game contains 2 components, `Player` and `Bullet`. `MyGame ` is a class that wraps around all components of the game and can control the child components.
+
+
+
+Let’s add flame to our app by adding the following to our `pubspec.yaml` file to install flame.
+
+```yaml
+flame: ^1.6.0
+```
+
+We can then create `MyGame` class. Add the following code in `lib/game.dart` file.
+
+```dart
+import 'dart:async';
+
+import 'package:flame/game.dart';
+import 'package:flame/components.dart';
+import 'package:flame/events.dart';
+import 'package:flame/image_composition.dart' as flame_image;
+import 'package:flame_realtime_shooting/game/bullet.dart';
+import 'package:flame_realtime_shooting/game/player.dart';
+import 'package:flutter/material.dart';
+
+class MyGame extends FlameGame with PanDetector, HasCollisionDetection {
+ MyGame({
+ required this.onGameOver,
+ required this.onGameStateUpdate,
+ });
+
+ static const _initialHealthPoints = 100;
+
+ /// Callback to notify the parent when the game ends.
+ final void Function(bool didWin) onGameOver;
+
+ /// Callback for when the game state updates.
+ final void Function(
+ Vector2 position,
+ int health,
+ ) onGameStateUpdate;
+
+ /// `Player` instance of the player
+ late Player _player;
+
+ /// `Player` instance of the opponent
+ late Player _opponent;
+
+ bool isGameOver = true;
+
+ int _playerHealthPoint = _initialHealthPoints;
+
+ late final flame_image.Image _playerBulletImage;
+ late final flame_image.Image _opponentBulletImage;
+
+ @override
+ Color backgroundColor() {
+ return Colors.transparent;
+ }
+
+ @override
+ Future? onLoad() async {
+ final playerImage = await images.load('player.png');
+ _player = Player(isMe: true);
+ final spriteSize = Vector2.all(Player.radius * 2);
+ _player.add(SpriteComponent(sprite: Sprite(playerImage), size: spriteSize));
+ add(_player);
+
+ final opponentImage = await images.load('opponent.png');
+ _opponent = Player(isMe: false);
+ _opponent.add(SpriteComponent.fromImage(opponentImage, size: spriteSize));
+ add(_opponent);
+
+ _playerBulletImage = await images.load('player-bullet.png');
+ _opponentBulletImage = await images.load('opponent-bullet.png');
+
+ await super.onLoad();
+ }
+
+ @override
+ void onPanUpdate(DragUpdateInfo info) {
+ _player.move(info.delta.global);
+ final mirroredPosition = _player.getMirroredPercentPosition();
+ onGameStateUpdate(mirroredPosition, _playerHealthPoint);
+ super.onPanUpdate(info);
+ }
+
+ @override
+ void update(double dt) {
+ super.update(dt);
+ if (isGameOver) {
+ return;
+ }
+ for (final child in children) {
+ if (child is Bullet && child.hasBeenHit && !child.isMine) {
+ _playerHealthPoint = _playerHealthPoint - child.damage;
+ final mirroredPosition = _player.getMirroredPercentPosition();
+ onGameStateUpdate(mirroredPosition, _playerHealthPoint);
+ _player.updateHealth(_playerHealthPoint / _initialHealthPoints);
+ }
+ }
+ if (_playerHealthPoint <= 0) {
+ endGame(false);
+ }
+ }
+
+ void startNewGame() {
+ isGameOver = false;
+ _playerHealthPoint = _initialHealthPoints;
+
+ for (final child in children) {
+ if (child is Player) {
+ child.position = child.initialPosition;
+ } else if (child is Bullet) {
+ child.removeFromParent();
+ }
+ }
+
+ _shootBullets();
+ }
+
+ /// shoots out bullets form both the player and the opponent.
+ ///
+ /// Calls itself every 500 milliseconds
+ Future _shootBullets() async {
+ await Future.delayed(const Duration(milliseconds: 500));
+
+ /// Player's bullet
+ final playerBulletInitialPosition = Vector2.copy(_player.position)
+ ..y -= Player.radius;
+ final playerBulletVelocities = [
+ Vector2(0, -100),
+ Vector2(60, -80),
+ Vector2(-60, -80),
+ ];
+ for (final bulletVelocity in playerBulletVelocities) {
+ add((Bullet(
+ isMine: true,
+ velocity: bulletVelocity,
+ image: _playerBulletImage,
+ initialPosition: playerBulletInitialPosition,
+ )));
+ }
+
+ /// Opponent's bullet
+ final opponentBulletInitialPosition = Vector2.copy(_opponent.position)
+ ..y += Player.radius;
+ final opponentBulletVelocities = [
+ Vector2(0, 100),
+ Vector2(60, 80),
+ Vector2(-60, 80),
+ ];
+ for (final bulletVelocity in opponentBulletVelocities) {
+ add((Bullet(
+ isMine: false,
+ velocity: bulletVelocity,
+ image: _opponentBulletImage,
+ initialPosition: opponentBulletInitialPosition,
+ )));
+ }
+
+ _shootBullets();
+ }
+
+ void updateOpponent({required Vector2 position, required int health}) {
+ _opponent.position = Vector2(size.x * position.x, size.y * position.y);
+ _opponent.updateHealth(health / _initialHealthPoints);
+ }
+
+ /// Called when either the player or the opponent has run out of health points
+ void endGame(bool playerWon) {
+ isGameOver = true;
+ onGameOver(playerWon);
+ }
+}
+```
+
+There is a lot going here, so let’s break it down. Within the `onLoad` method, we are loading all of the sprites used throughout the game. Then we are adding the player and opponent component.
+
+Within `onPanUpdate`, we handle the user dragging on the screen. Note that we are calling the `onGameStateUpdate` callback to pass the player’s position so that we can share it to the opponent’s client later when we handle network connections. On the other hand, we have the `updateOpponent` method, which is used to update the opponent’s state with the information coming in from the network. We will also add logic to call it from the Flutter widgets later.
+
+Upon starting the game, `_shootBullets()` is called, which shoots out bullets both from the player and the opponent. `_shootBullets()` is a recursive function that calls itself every 500 milliseconds. If the bullet hits the player, it is caught inside the `udpate()` method, which is called on every frame. There we calculate the new player’s health points.
+
+### Creating the Player Component
+
+`Player` component has the UFO sprite and represents the player and the opponent. It extends the `PositionComponent` from Flame. Add the following in `lib/player.dart`
+
+```dart
+import 'dart:async';
+
+import 'package:flame/collisions.dart';
+import 'package:flame/components.dart';
+import 'package:flame_realtime_shooting/game/bullet.dart';
+import 'package:flutter/material.dart';
+
+class Player extends PositionComponent with HasGameRef, CollisionCallbacks {
+ Vector2 velocity = Vector2.zero();
+
+ late final Vector2 initialPosition;
+
+ Player({required bool isMe}) : _isMyPlayer = isMe;
+
+ /// Whether it's me or the opponent
+ final bool _isMyPlayer;
+
+ static const radius = 30.0;
+
+ @override
+ Future? onLoad() async {
+ anchor = Anchor.center;
+ width = radius * 2;
+ height = radius * 2;
+
+ final initialX = gameRef.size.x / 2;
+ initialPosition = _isMyPlayer
+ ? Vector2(initialX, gameRef.size.y * 0.8)
+ : Vector2(initialX, gameRef.size.y * 0.2);
+ position = initialPosition;
+
+ add(CircleHitbox());
+ add(_Gauge());
+ await super.onLoad();
+ }
+
+ void move(Vector2 delta) {
+ position += delta;
+ }
+
+ void updateHealth(double healthLeft) {
+ for (final child in children) {
+ if (child is _Gauge) {
+ child._healthLeft = healthLeft;
+ }
+ }
+ }
+
+ @override
+ void onCollision(Set intersectionPoints, PositionComponent other) {
+ super.onCollision(intersectionPoints, other);
+ if (other is Bullet && _isMyPlayer != other.isMine) {
+ other.hasBeenHit = true;
+ other.removeFromParent();
+ }
+ }
+
+ /// returns the mirrored percent position of the player
+ /// to be broadcasted to other clients
+ Vector2 getMirroredPercentPosition() {
+ final mirroredPosition = gameRef.size - position;
+ return Vector2(mirroredPosition.x / gameRef.size.x,
+ mirroredPosition.y / gameRef.size.y);
+ }
+}
+
+class _Gauge extends PositionComponent {
+ double _healthLeft = 1.0;
+
+ @override
+ FutureOr onLoad() {
+ final playerParent = parent;
+ if (playerParent is Player) {
+ width = playerParent.width;
+ height = 10;
+ anchor = Anchor.centerLeft;
+ position = Vector2(0, 0);
+ }
+ return super.onLoad();
+ }
+
+ @override
+ void render(Canvas canvas) {
+ super.render(canvas);
+ canvas.drawRect(
+ Rect.fromPoints(
+ const Offset(0, 0),
+ Offset(width, height),
+ ),
+ Paint()..color = Colors.white);
+ canvas.drawRect(
+ Rect.fromPoints(
+ const Offset(0, 0),
+ Offset(width * _healthLeft, height),
+ ),
+ Paint()
+ ..color = _healthLeft > 0.5
+ ? Colors.green
+ : _healthLeft > 0.25
+ ? Colors.orange
+ : Colors.red);
+ }
+}
+```
+
+You can see that it has a `_isMyPlayer` property, which is true for the player and false for the opponent. If we take a look at the `onLoad` method, we can use this to position it either at the top if it’s the opponent, or at the bottom if it’s the player. We can also see that we are adding a `CircleHitbox`, because we need to detect collisions between it and the bullets. Lastly, we are adding `_Gauge` as its child, which is the health point gauge you see on top of each players. Within `onCollision` callback, we are checking if the collided object is the opponent’s bullet, and if it is, we flag the bullet as `hasBeenHit` and remove it from the game.
+
+`getMirroredPercentPosition` method is used when sharing the position with the opponent’s client. It calculates the mirrored position of the player. `updateHealth` is called when the health changes and updates the bar length of the `_Gauge` class.
+
+### Adding bullets
+
+Lastly we will add the `Bullet` class. It represents a single bullet coming out from the player and the opponent. Within `onLoad` it adds the sprite to apply the nice image and `CircleHitbox` so that it can collide with other objects. You can also see that it receives a `velocity` in the constructor, the position is updated using the velocity and the elapsed time within the `update` method. This is how you can have it move in a single direction at a constant speed.
+
+```dart
+import 'dart:async';
+
+import 'package:flame/collisions.dart';
+import 'package:flame/components.dart';
+import 'package:flame/image_composition.dart' as flame_image;
+
+class Bullet extends PositionComponent with CollisionCallbacks, HasGameRef {
+ final Vector2 velocity;
+
+ final flame_image.Image image;
+
+ static const radius = 5.0;
+
+ bool hasBeenHit = false;
+
+ final bool isMine;
+
+ /// Damage that it deals when it hits the player
+ final int damage = 5;
+
+ Bullet({
+ required this.isMine,
+ required this.velocity,
+ required this.image,
+ required Vector2 initialPosition,
+ }) : super(position: initialPosition);
+
+ @override
+ Future? onLoad() async {
+ anchor = Anchor.center;
+
+ width = radius * 2;
+ height = radius * 2;
+
+ add(CircleHitbox()
+ ..collisionType = CollisionType.passive
+ ..anchor = Anchor.center);
+
+ final sprite =
+ SpriteComponent.fromImage(image, size: Vector2.all(radius * 2));
+
+ add(sprite);
+ await super.onLoad();
+ }
+
+ @override
+ void update(double dt) {
+ super.update(dt);
+ position += velocity * dt;
+
+ if (position.y < 0 || position.y > gameRef.size.y) {
+ removeFromParent();
+ }
+ }
+}
+```
+
+### Step 4. Add real-time communications between players
+
+At this point, we have a working shooting game except the opponent does not move, because we have not added any ways to communicate between clients. We will use Supabase’s [realtime features](https://supabase.com/docs/guides/realtime) for this, because it gives us an out of the box solution to handle low-latency real-time communication between players. If you do not have a Supabase project created yet, head over to [database.new](https://database.new/) to create one.
+
+Before we get into any coding, let’s install the Supabase SDK into our app. We will also use the [uuid package](https://pub.dev/packages/uuid) to generate random unique ids for the users. Add the following to `pubspec.yaml` file.
+
+```yaml
+supabase_flutter: ^1.4.0
+uuid: ^3.0.7
+```
+
+Once `pub get` is complete, let’s initialize Supabase. We will override the `main` function to initialize Supabase. You can get your Supabase URL and Anon Key at `Project Setting` > `API`. Copy and paste them into the `Supabase.initialize` call.
+
+```dart
+void main() async {
+await Supabase.initialize(
+url: 'YOUR_SUPABASE_URL',
+anonKey: 'YOUR_ANON_KEY',
+realtimeClientOptions: const RealtimeClientOptions(eventsPerSecond: 40),
+);
+runApp(const MyApp());
+}
+
+// Extract Supabase client for easy access to Supabase
+final supabase = Supabase.instance.client;
+```
+
+`RealtimeClientOptions` here is a parameter to override how many events per second each client will send to Supabase. The default is 10, but we want to override to 40 to provide a more in-synced experience.
+
+With this, we are ready to get into adding the real-time features now.
+
+### Handle the Lobby to wait for Other Players to show up
+
+We will start by rewriting the `_Lobby` class the first thing we have to do in the lobby is to wait and detect other online users also at the lobby. We can implement this using the [presence](https://supabase.com/docs/guides/realtime/presence) feature in Supabase.
+
+Add `initState` and inside it initialize a `RealtimeChannel` instance. We can call it `_lobbyChannel`. If we take a look at the `subscribe()` method, we can see that upon successful subscription to the lobby channel, we are tracking our the unique user ID that we create uplon initialization. We are listening to the `sync` event to get notified whenever anyone is “present”. Within the callback, we are extracting the userIds of all the users in the lobby and set it as the state.A game starts when someone taps on the `Start` button. If we take a look at the `onPressed` callback, we see that we are sending a [broadcast](https://supabase.com/docs/guides/realtime/broadcast) event to the lobby channel with two participant ids and a randomly generated game ID. [Broadcast](https://supabase.com/docs/guides/realtime/broadcast) is a feature of Supabase to send and receive lightweight low-latency data between clients, and when the two participants, one of them being the person tapping on the `start` button, is received on both ends, a game starts. We can observe within `initState` inside the callback for `game_start` event that upon receiving a broadcast event, it checks if the player is one of the participants, and if it is, it will call the `onGameStarted` call back and pop the navigator removing the dialog. The game has begun!
+
+```dart
+class _LobbyDialogState extends State<_LobbyDialog> {
+ List _userids = [];
+ bool _loading = false;
+
+ /// Unique identifier for each players to identify eachother in lobby
+ final myUserId = const Uuid().v4();
+
+ late final RealtimeChannel _lobbyChannel;
+
+ @override
+ void initState() {
+ super.initState();
+
+ _lobbyChannel = supabase.channel(
+ 'lobby',
+ opts: const RealtimeChannelConfig(self: true),
+ );
+ _lobbyChannel.on(RealtimeListenTypes.presence, ChannelFilter(event: 'sync'),
+ (payload, [ref]) {
+ // Update the lobby count
+ final presenceState = _lobbyChannel.presenceState();
+
+ setState(() {
+ _userids = presenceState.values
+ .map((presences) =>
+ (presences.first as Presence).payload['user_id'] as String)
+ .toList();
+ });
+ }).on(RealtimeListenTypes.broadcast, ChannelFilter(event: 'game_start'),
+ (payload, [_]) {
+ // Start the game if someone has started a game with you
+ final participantIds = List.from(payload['participants']);
+ if (participantIds.contains(myUserId)) {
+ final gameId = payload['game_id'] as String;
+ widget.onGameStarted(gameId);
+ Navigator.of(context).pop();
+ }
+ }).subscribe(
+ (status, [ref]) async {
+ if (status == 'SUBSCRIBED') {
+ await _lobbyChannel.track({'user_id': myUserId});
+ }
+ },
+ );
+ }
+
+ @override
+ void dispose() {
+ supabase.removeChannel(_lobbyChannel);
+ super.dispose();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return AlertDialog(
+ title: const Text('Lobby'),
+ content: _loading
+ ? const SizedBox(
+ height: 100,
+ child: Center(child: CircularProgressIndicator()),
+ )
+ : Text('${_userids.length} users waiting'),
+ actions: [
+ TextButton(
+ onPressed: _userids.length < 2
+ ? null
+ : () async {
+ setState(() {
+ _loading = true;
+ });
+
+ final opponentId =
+ _userids.firstWhere((userId) => userId != myUserId);
+ final gameId = const Uuid().v4();
+ await _lobbyChannel.send(
+ type: RealtimeListenTypes.broadcast,
+ event: 'game_start',
+ payload: {
+ 'participants': [
+ opponentId,
+ myUserId,
+ ],
+ 'game_id': gameId,
+ },
+ );
+ },
+ child: const Text('start'),
+ ),
+ ],
+ );
+ }
+}
+```
+
+### Sharing Game States with the Opposing Player
+
+Once a game begins, we need to synchronize the game states between the two clients. In our case, we will sync only the player’s position and health points. Whenever a player moves, or the player’s health points change, the `onGameStateUpdate` callback on our `MyGame` instance that will fire notifying the update along with its position and health point. We will broadcast those information to the opponent’s client via Supabase [broadcast feature](https://supabase.com/docs/guides/realtime/broadcast).
+
+Fill in the `_initialize` method like the following to initialize the game.
+
+```dart
+class GamePage extends StatefulWidget {
+ const GamePage({Key? key}) : super(key: key);
+
+ @override
+ State createState() => _GamePageState();
+}
+
+class _GamePageState extends State {
+ late final MyGame _game;
+
+ /// Holds the RealtimeChannel to sync game states
+ RealtimeChannel? _gameChannel;
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ body: Stack(
+ fit: StackFit.expand,
+ children: [
+ Image.asset('assets/images/background.jpg', fit: BoxFit.cover),
+ GameWidget(game: _game),
+ ],
+ ),
+ );
+ }
+
+ @override
+ void initState() {
+ super.initState();
+ _initialize();
+ }
+
+ Future _initialize() async {
+ _game = MyGame(
+ onGameStateUpdate: (position, health) async {
+ ChannelResponse response;
+ // Loop until the send succeeds if the payload is to notify defeat.
+ do {
+ response = await _gameChannel!.send(
+ type: RealtimeListenTypes.broadcast,
+ event: 'game_state',
+ payload: {'x': position.x, 'y': position.y, 'health': health},
+ );
+
+ // wait for a frame to avoid infinite rate limiting loops
+ await Future.delayed(Duration.zero);
+ setState(() {});
+ } while (response == ChannelResponse.rateLimited && health <= 0);
+ },
+ onGameOver: (playerWon) async {
+ await showDialog(
+ barrierDismissible: false,
+ context: context,
+ builder: ((context) {
+ return AlertDialog(
+ title: Text(playerWon ? 'You Won!' : 'You lost...'),
+ actions: [
+ TextButton(
+ onPressed: () async {
+ Navigator.of(context).pop();
+ await supabase.removeChannel(_gameChannel!);
+ _openLobbyDialog();
+ },
+ child: const Text('Back to Lobby'),
+ ),
+ ],
+ );
+ }),
+ );
+ },
+ );
+
+ // await for a frame so that the widget mounts
+ await Future.delayed(Duration.zero);
+
+ if (mounted) {
+ _openLobbyDialog();
+ }
+ }
+
+ void _openLobbyDialog() {
+ showDialog(
+ context: context,
+ barrierDismissible: false,
+ builder: (context) {
+ return _LobbyDialog(
+ onGameStarted: (gameId) async {
+ // await a frame to allow subscribing to a new channel in a realtime callback
+ await Future.delayed(Duration.zero);
+
+ setState(() {});
+
+ _game.startNewGame();
+
+ _gameChannel = supabase.channel(gameId,
+ opts: const RealtimeChannelConfig(ack: true));
+
+ _gameChannel!.on(RealtimeListenTypes.broadcast,
+ ChannelFilter(event: 'game_state'), (payload, [_]) {
+ final position =
+ Vector2(payload['x'] as double, payload['y'] as double);
+ final opponentHealth = payload['health'] as int;
+ _game.updateOpponent(
+ position: position,
+ health: opponentHealth,
+ );
+
+ if (opponentHealth <= 0) {
+ if (!_game.isGameOver) {
+ _game.isGameOver = true;
+ _game.onGameOver(true);
+ }
+ }
+ }).subscribe();
+ },
+ );
+ });
+ }
+}
+```
+
+You can see that within `_openLobbyDialog`, there is an `onGameStarted` callback for when the game has started. Once a game has been started, it creates a new channel using the game ID as the channel name and starts listening to game state updates from the opponent.You can see that within the `onGameOver` callback, we are showing a simple dialog. Upon tapping `Back to Lobby`, the user will be taken back to the lobby dialog, where they can start another game if they want to.
+
+
+
+With all of that put together, we have a functioning real-time multiplayer shooting game. Grab a friend, run the app with `flutter run`, and have fun with it!
+
+## Conclusions
+
+We learned how to create an interactive shooting game. We took advantage of Flutter’s dialogs to create a quick and easy lobby and post-game UI. Then we created the game using Flame. We learned how to detect and handle collisions and experienced how easy creating a sophisticated game was using Flame. Finally, we added capabilities to share game states with other clients to complete a real-time multiplayer experience without managing our own infrastructure using [Supabase](https://supabase.com/).
+
+## More Flutter Resources
+
+- [Complete code of this article](https://github.com/supabase/supabase/tree/master/examples/realtime/flutter-multiplayer-shooting-game)
+- [Flutter Tutorial: building a Flutter chat app](https://supabase.com/blog/flutter-tutorial-building-a-chat-app)
+- [Flutter Authentication and Authorization with RLS](https://supabase.com/blog/flutter-authentication-and-authorization-with-rls)
+- [Generate Flame template using Very Good CLI](https://verygood.ventures/blog/generate-a-game-with-our-new-template)
+- [Supabase Flutter SDK docs](https://supabase.com/docs/reference/dart/start)
diff --git a/apps/www/_blog/2023-02-17-type-constraints-in-65-lines-of-sql.mdx b/apps/www/_blog/2023-02-17-type-constraints-in-65-lines-of-sql.mdx
new file mode 100644
index 00000000000..262c4630353
--- /dev/null
+++ b/apps/www/_blog/2023-02-17-type-constraints-in-65-lines-of-sql.mdx
@@ -0,0 +1,293 @@
+---
+title: Type Constraints in 65 lines of SQL
+description: Creating validated data types in Postgres
+author: oli_rice
+image: 2023-jan/semver-thumb.jpg
+thumb: 2023-jan/semver-thumb.jpg
+tags:
+ - postgres
+ - planetpg
+date: '2023-02-17'
+toc_depth: 3
+---
+
+PostgreSQL has a rich and extensible type system. Beyond [enums](https://www.postgresql.org/docs/current/datatype-enum.html) and [composite](https://www.postgresql.org/docs/current/rowtypes.html) types, we can:
+
+- apply data validation rules
+- override comparison operators like `=` / `+` / `-`
+- create custom aggregations
+- define casting rules between types
+
+With a little effort, a user-defined type can feel indistinguishable from a built-in. In this article we focus on validation and ergonomics while quickly touching on a few other concepts.
+
+To illustrate, we’ll create an `semver` data type to represent [Semantic Versioning](https://semver.org) values. We’ll then add validation rules to make invalid states unrepresentable.
+
+## SemVer
+
+A (very) loose primer on SemVer:
+
+SemVer is a specification for representing software versions that communicate information about backwards compatibility. The type is typically represented as a string with 5 components.
+
+
+
+Where `pre-release` and `metadata` are optional.
+
+The intent of each component is outside the scope of this article but, as an example, incrementing the major version number notifies users that the release includes at least one backwards incompatible change.
+
+For a concise representation of the full spec, [check out the grammar](https://semver.org/#backusnaur-form-grammar-for-valid-semver-versions).
+
+## SQL
+
+For our purposes, we’ll assume that the SemVer type is a critical component of the application that needs to be queried flexibly and efficiently.
+
+### Storing Components
+
+To that end, we’ll store each component of the version as a separate field on a [composite type](https://www.postgresql.org/docs/current/rowtypes.html).
+
+{/* prettier-ignore */}
+```sql
+create type semver_components as (
+ major int,
+ minor int,
+ patch int,
+ pre_release text[],
+ build_metadata text[]
+);
+```
+
+We can create an instance of this type in SQL by casting a tuple as the `semver_components` type.
+
+{/* prettier-ignore */}
+```sql
+select
+ (1, 2, 3, array['beta', '1'], array['meta'])::semver_components
+-- returns: (1,2,3,{'beta','1'},{'meta'})
+```
+
+Unfortunately, our definition is far too permissive.
+
+{/* prettier-ignore */}
+```sql
+select
+ (null, -500, null, array['?'], array[''])::semver_components
+-- returns: (,-500,,{'?'},{''
+```
+
+Our data type has no problem accepting invalid components. To list a few of the SemVer rules we violated:
+
+- Major version must not be null
+- Minor version must be ≥ 0
+- Patch version must not be null
+- Pre-release elements must only include characters [A-z0-9]
+- Build metadata elements may not be empty strings
+
+We need to add some validation rules to meet our “make invalid states unrepresentable” goal.
+
+### Validation
+
+[Domains](https://www.postgresql.org/docs/current/sql-createdomain.html) are Postgres’ solution for optionally layering constraints over a data type. Domains are to types what [check constraints](https://www.postgresql.org/docs/current/ddl-constraints.html) are to tables. If you’re not familiar with check constraints, you can think of them as equivalent to zod/pydantic in javascript/python.
+
+Let's codify some SemVer rules, layer them on the `semver_components` type, and give the new domain a friendly name.
+
+{/* prettier-ignore */}
+```sql
+create domain semver
+ as semver_components
+ check (
+ -- major: non-null positive integer
+ (value).major is not null and (value).major >= 0
+ -- minor: non-null positive integer
+ and (value).minor is not null and (value).minor >= 0
+ -- patch: non-null positive integer
+ and (value).patch is not null and (value).patch >= 0
+ and semver_elements_match_regex(
+ (value).pre_release,
+ '^[A-z0-9]{1,255}$'
+ )
+ and semver_elements_match_regex(
+ (value).build_metadata,
+ '^[A-z0-9\.]{1,255}$'
+ )
+ );
+```
+
+which references a helper function:
+
+{/* prettier-ignore */}
+```sql
+create or replace function semver_elements_match_regex(
+ parts text[],
+ regex text
+)
+returns bool
+language sql
+as $$
+ -- validates that *parts* nullable array of non-empty strings
+ -- where each element of *parts* matches *regex*
+ select
+ $1 is null
+ or (
+ (
+ select (
+ bool_and(pr_arr.elem is not null)
+ and bool_and(pr_arr.elem ~ $2)
+ )
+ from
+ unnest($1) pr_arr(elem)
+ )
+ and array_length($1, 1) > 0
+ )
+$$;
+```
+
+Now, if we repeat our positive and negative test cases using the `semver` type (vs `semver_components`) we still accept valid states:
+
+{/* prettier-ignore */}
+```sql
+-- Success Case
+select
+ (1, 2, 3, array['beta', '1'], array['meta'])::semver,
+-- returns: (1,2,3,{'beta','1'},{'meta'})
+```
+
+while invalid states are rejected with an error:
+
+{/* prettier-ignore */}
+```sql
+-- Failure Case
+select
+ (null, -500, null, array['?'], array[''])::semver
+-- ERROR: value for domain semver violates check constraint "semver_check"
+-- SQL state: 23514
+```
+
+### Testing
+
+Our validation doesn’t have to be called manually. The `semver` domain can be used anywhere you’d use the `semver_components` type and the validations are automatically applied.
+
+{/* prettier-ignore */}
+```sql
+-- A table with a semver column
+create table package_version(
+ id bigserial primary key,
+ package_name text not null,
+ package_semver semver not null -- semver column
+);
+
+-- Insert some valid records
+insert into package_version( package_name, package_semver )
+values
+ ('supabase-js', (2, 2, 3, null, null)),
+ ('supabase-js', (2, 0, 0, array['rc', '1'], null)
+);
+
+-- Attempt to insert an invalid record (major is null)
+insert into package_version( package_name, package_semver )
+values
+ ('invalid-js', (null, 1, 0, array['asdf'], null));
+-- ERROR: value for domain semver violates check constraint "semver_check"
+```
+
+Good stuff!
+
+We’re 48 lines of SQL in and have solved for making invalid states unrepresentable. Now lets think about ergonomics.
+
+### Displaying
+
+Now that our data type is well constrained, you might notice that selecting values from a `semver` typed column returns a tuple, rather than the SemVer string we’re used to seeing.
+
+{/* prettier-ignore */}
+```sql
+select
+ *
+from
+ package_version
+/*
+id | package_name | package_semver
+-------------------------------------
+ 1 | supabase-js | (2,2,3,,)
+ 2 | supabase-js | (2,0,0,"{rc,1}",)
+*/
+```
+
+For example: `(2,0,0,"{rc,1}",)` vs `2.0.0-rc.1`
+
+We could work around that problem with some [custom casts](https://www.postgresql.org/docs/current/sql-createcast.html), but I’d recommend keeping everything explicit with a function call.
+
+{/* prettier-ignore */}
+```sql
+create or replace function semver_to_text(semver)
+ returns text
+ immutable
+ language sql
+as $$
+ select
+ format('%s.%s.%s', $1.major, $1.minor, $1.patch)
+ || case
+ when $1.pre_release is null then ''
+ else format('-%s', array_to_string($1.pre_release, '.'))
+ end
+ || case
+ when $1.build_metadata is null then ''
+ else format('+%s', array_to_string($1.build_metadata, '.'))
+ end
+$$;
+```
+
+Which allows us to query the `package_version` table and retrieve a string representation of the data.
+
+{/* prettier-ignore */}
+```sql
+select
+ id,
+ package_name,
+ semver_to_text(package_semver) as ver -- cast as text
+from
+ package_version
+/*
+id | package_name | ver
+------------------------------
+ 1 | supabase-js | 2.2.3
+ 2 | supabase-js | 2.0.0-rc.1
+*/
+```
+
+Or, better yet, use a [generated column](https://www.postgresql.org/docs/current/ddl-generated-columns.html)
+
+{/* prettier-ignore */}
+```sql
+create table package_version(
+ id bigserial primary key,
+ package_name text not null,
+ package_semver semver not null,
+ semver_text text generated always as (semver_to_text(package_semver)) stored
+);
+```
+
+so the text representation is persisted along with the `semver` type and incurs no query/filter penalty.
+
+### Other Tricks
+
+Postgres provides all the tools you could want to make your data types/domains work with SQL as seamlessly as builtins.
+
+For example, you could:
+
+- add convenience functions to parse a [semver type from text](https://github.com/supabase/dbdev/blob/ca338584203d9b2eb7a4a378f5724674c15b9c25/supabase/migrations/20220117141507_semver.sql#L78)
+- [override the equality operator](https://github.com/supabase/dbdev/blob/ca338584203d9b2eb7a4a378f5724674c15b9c25/supabase/migrations/20220117141507_semver.sql#L37-L63) (`=`) to correctly reflect that versions differing only in build metadata are considered equal
+- [add a `max` function](https://github.com/supabase/dbdev/blob/ca338584203d9b2eb7a4a378f5724674c15b9c25/supabase/migrations/20220117141507_semver.sql#L122-L140) to efficiently query for the newest version of each package from within the database
+
+to name a few.
+
+Aligning the right parts of your business’ logic with the database can dramatically improve throughput, decrease IO, and simplify application code.
+
+### Conclusion
+
+Admittedly, building performant and ergonomic custom data types in Postgres involves a lot of ceremony.
+
+That said, in cases where:
+
+- the type’s data integrity is critical
+- the type is well specified
+- the type’s spec does not change (or changes infrequently)
+
+Teaching Postgres to have first class support for your custom type can be transformative for data integrity and performance.
diff --git a/apps/www/components/AuthWidget/AuthWidgetSection.tsx b/apps/www/components/AuthWidget/AuthWidgetSection.tsx
index 047ff5c4ac7..f9ee1bbf25a 100644
--- a/apps/www/components/AuthWidget/AuthWidgetSection.tsx
+++ b/apps/www/components/AuthWidget/AuthWidgetSection.tsx
@@ -3,10 +3,11 @@ import { Button } from 'ui'
import { useState } from 'react'
// Import Swiper React components
import { useRouter } from 'next/router'
+import Image from 'next/image'
import { ColorSwatchIcon, MenuIcon } from '@heroicons/react/outline'
import { Auth, ThemeSupa } from '@supabase/auth-ui-react'
-import { useTheme } from '../Providers'
+import { useTheme } from 'common/Providers'
const supabase = createClient(
'https://rsnibhkhsbfnncjmwnkj.supabase.co',
@@ -120,7 +121,14 @@ function AuthWidgetSection() {
match your brand and aesthetic
+ Every project on the Supabase Platform comes with its own dedicated Postgres
+ instance running inside a virtual machine (VM). The following table describes the
+ base instance with additional compute add-ons available if you need extra
+ performance when scaling up Supabase.
+