Merge pull request #14732 from supabase/master

Prod deploy dashboard
This commit is contained in:
Alaister Young authored and GitHub committed 2023-06-01 21:58:21 +10:00
commit aa640eee5e
232 files changed
+9562 -1879

No files matched your search

@@ -0,0 +1,193 @@
import { useState } from 'react'
import { Input, Button } from 'ui'
import Admonition from '~/components/Admonition'
function base64URL(value: string) {
return globalThis.btoa(value).replace(/[=]/g, '').replace(/[+]/g, '-').replace(/[\/]/g, '_')
}
/*
Convert a string into an ArrayBuffer
from https://developers.google.com/web/updates/2012/06/How-to-convert-ArrayBuffer-to-and-from-String
*/
function stringToArrayBuffer(value: string) {
const buf = new ArrayBuffer(value.length)
const bufView = new Uint8Array(buf)
for (let i = 0; i < value.length; i++) {
bufView[i] = value.charCodeAt(i)
}
return buf
}
function arrayBufferToString(buf) {
return String.fromCharCode.apply(null, new Uint8Array(buf))
}
const generateAppleSecretKey = async (
kid: string,
iss: string,
sub: string,
file: File
): Promise<{ kid: string; jwt: string; exp: number }> => {
if (!kid) {
const match = file.name.match(/AuthKey_([^.]+)[.].*$/i)
if (match && match[1]) {
kid = match[1]
}
}
if (!kid) {
throw new Error(
`No Key ID provided. The file "${file.name}" does not follow the AuthKey_XXXXXXXXXX.p8 pattern. Please provide a Key ID manually.`
)
}
const contents = await file.text()
if (!contents.match(/^\s*-+BEGIN PRIVATE KEY-+[^-]+-+END PRIVATE KEY-+\s*$/i)) {
throw new Error(`Chosen file does not appear to be a PEM encoded PKCS8 private key file.`)
}
// remove PEM headers and spaces
const pkcs8 = stringToArrayBuffer(
globalThis.atob(contents.replace(/-+[^-]+-+/g, '').replace(/\s+/g, ''))
)
const privateKey = await globalThis.crypto.subtle.importKey(
'pkcs8',
pkcs8,
{
name: 'ECDSA',
namedCurve: 'P-256',
},
true,
['sign']
)
const iat = Math.floor(Date.now() / 1000)
const exp = iat + 180 * 24 * 60 * 60
const jwt = [
base64URL(JSON.stringify({ typ: 'JWT', kid, alg: 'ES256' })),
base64URL(
JSON.stringify({
iss,
sub,
iat,
exp,
aud: 'https://appleid.apple.com',
})
),
]
const signature = await globalThis.crypto.subtle.sign(
{
name: 'ECDSA',
hash: 'SHA-256',
},
privateKey,
stringToArrayBuffer(jwt.join('.'))
)
jwt.push(base64URL(arrayBufferToString(signature)))
return { kid, jwt: jwt.join('.'), exp }
}
const AppleSecretGenerator = () => {
const [file, setFile] = useState({ file: null as File | null })
const [teamID, setTeamID] = useState('')
const [serviceID, setServiceID] = useState('')
const [keyID, setKeyID] = useState('')
const [secretKey, setSecretKey] = useState('')
const [expiresAt, setExpiresAt] = useState('')
const [error, setError] = useState('')
return (
<>
<Input
label="Account ID"
labelOptional="required"
placeholder="Apple Developer account ID, 10 alphanumeric digits"
descriptionText="Found in the upper-right corner of Apple Developer Center."
value={teamID}
onChange={(e) => setTeamID(e.target.value.trim())}
/>
<Input
label="Service ID"
labelOptional="required"
placeholder="ID of the service, example: com.example.app.service"
descriptionText="Found under Certificates, Identifiers & Profiles in Apple Developer Center."
value={serviceID}
onChange={(e) => setServiceID(e.target.value.trim())}
/>
<Input
label="Key ID"
labelOptional="(optional)"
placeholder="Extracted from filename, AuthKey_XXXXXXXXXX.p8"
descriptionText="If the file you select does not preserve the original name from Apple Developer Center, please enter the key ID."
value={keyID}
onChange={(e) => setKeyID(e.target.value.trim())}
/>
<div>
<input
type="file"
onChange={(e) => {
setFile({ file: e.target.files[0] })
}}
/>
</div>
<div style={{ height: '1rem' }} />
<Button
size="medium"
disabled={
!(
teamID.length === 10 &&
serviceID &&
((globalThis && globalThis.showOpenFilePicker) || file.file)
)
}
onClick={async () => {
setError('')
try {
const { kid, jwt, exp } = await generateAppleSecretKey(
keyID,
teamID,
serviceID,
file.file
)
setKeyID(kid)
setSecretKey(jwt)
setExpiresAt(new Date(exp * 1000).toString())
setError('')
} catch (e: any) {
setError(e.message)
console.error(e)
}
}}
>
Generate Secret Key
</Button>
{error && <Admonition type="danger">{error}</Admonition>}
{secretKey && (
<>
<div style={{ height: '1rem' }} />
<Input
label="Secret Key"
value={secretKey}
descriptionText={`Valid until: ${expiresAt}. Make sure you generate a new one before then!`}
reveal
copy
size="medium"
/>
</>
)}
</>
)
}
export default AppleSecretGenerator
@@ -0,0 +1,18 @@
import { Tabs } from 'ui'
export const TabPanel = Tabs.Panel
## Project setup
Let's create a new Postgres database. This is as simple as starting a new Project in Supabase:
1. [Create a new project](https://database.new/) in the Supabase dashboard.
1. Enter your project details. Remember to store your password somewhere safe.
Your database will be available in less than a minute.
**Finding your credentials:**
You can find your project credentials inside the project [settings](https://app.supabase.com/project/_/settings/), including:
- [Database credentials](https://app.supabase.com/project/_/settings/database): connection strings and connection pooler details.
- [API credentials](https://app.supabase.com/project/_/settings/database): your serverless API URL and `anon` / `service_role` keys.
@@ -373,10 +373,11 @@ export function IconMenuAI({ width = 16, height = 16 }: HomeMenuIcon) {
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M7.59777 3.20772C7.25701 4.22996 6.68287 5.15901 5.92094 5.92094C5.15901 6.68288 4.23014 7.25695 3.2079 7.59771L2 8L3.20772 8.40223C4.22996 8.74299 5.15901 9.31712 5.92094 10.0791C6.68287 10.841 7.25695 11.7699 7.59771 12.7921L8 14L8.40223 12.7923C8.74299 11.77 9.31713 10.841 10.0791 10.0791C10.841 9.31712 11.7699 8.74305 12.7921 8.40229L14 8L12.7923 7.59777C11.77 7.25701 10.841 6.68288 10.0791 5.92094C9.31713 5.15901 8.74305 4.23014 8.40229 3.2079L8 2L7.59777 3.20772Z"
d="M7.99886 7.63216V14.4892M7.99886 7.63216L14.0488 4.11804M7.99886 7.63216L1.94922 4.11819M1.94922 4.11819V8.32332M1.94922 4.11819V4.08217L5.57319 1.97717M14.049 8.36007V4.08217L10.4251 1.97717M11.8165 12.4072L7.99913 14.6245L4.18177 12.4072"
stroke="currentColor"
strokeMiterlimit="10"
strokeLinejoin="bevel"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
)
@@ -508,14 +508,33 @@ export const database: NavMenuConstant = {
title: 'Database',
url: '/guides/database',
items: [
{ name: 'Database Connections', url: '/guides/database/connecting-to-postgres' },
{ name: 'Tables and Data', url: '/guides/database/tables' },
{ name: 'Database Functions', url: '/guides/database/functions' },
{ name: 'Database Webhooks', url: '/guides/database/webhooks' },
{ name: 'Full Text Search', url: '/guides/database/full-text-search' },
{ name: 'Database Testing', url: '/guides/database/testing' },
{ name: 'Managing Secrets with Vault', url: '/guides/database/vault' },
{ name: 'Column Encryption', url: '/guides/database/column-encryption' },
{ name: 'Overview', url: '/guides/database' },
{
name: 'Fundamentals',
url: undefined,
items: [
{ name: 'Connecting to your database', url: '/guides/database/connecting-to-postgres' },
{ name: 'Managing tables, views, and data', url: '/guides/database/tables' },
{ name: 'Managing database functions', url: '/guides/database/functions' },
{ name: 'Managing indexes', url: '/guides/database/postgres/indexes' },
{ name: 'Managing database webhooks', url: '/guides/database/webhooks' },
{ name: 'Managing database replication', url: '/guides/database/replication' },
{ name: 'Managing secrets with Vault', url: '/guides/database/vault' },
],
},
{
name: 'Postgres Guides',
url: undefined,
items: [
{ name: 'Implementing Full Text Search', url: '/guides/database/full-text-search' },
{ name: 'Implementing Cascade Deletes', url: '/guides/database/postgres/cascade-deletes' },
{ name: 'Implementing column encryption', url: '/guides/database/column-encryption' },
{ name: 'Testing your database', url: '/guides/database/testing' },
{ name: 'Managing Timeouts', url: '/guides/database/timeouts' },
{ name: 'Managing Passwords', url: '/guides/database/managing-passwords' },
{ name: 'Configuring Timezones', url: '/guides/database/managing-timezones' },
],
},
{
name: 'Extensions',
url: undefined,
@@ -622,17 +641,9 @@ export const database: NavMenuConstant = {
],
},
{
name: 'Postgres resources',
name: 'Examples',
url: undefined,
items: [
{
name: 'Managing Indexes',
url: '/guides/database/postgres/indexes',
},
{
name: 'Cascade Deletes',
url: '/guides/database/postgres/cascade-deletes',
},
{
name: 'Drop All Tables in Schema',
url: '/guides/database/postgres/dropping-all-tables-in-schema',
@@ -647,16 +658,6 @@ export const database: NavMenuConstant = {
},
],
},
{
name: 'Configuration',
url: undefined,
items: [
{ name: 'Timeouts', url: '/guides/database/timeouts' },
{ name: 'Replication', url: '/guides/database/replication' },
{ name: 'Passwords', url: '/guides/database/managing-passwords' },
{ name: 'Timezones', url: '/guides/database/managing-timezones' },
],
},
],
}
@@ -726,7 +727,7 @@ export const functions: NavMenuConstant = {
url: undefined,
items: [
{ name: 'Developing Functions locally', url: '/guides/functions/local-development' },
{ name: 'Deploying with Git', url: '/guides/functions/cicd-workflow' },
{ name: 'Deploying with GitHub', url: '/guides/functions/cicd-workflow' },
{ name: 'Managing Secrets and Environment Variables', url: '/guides/functions/secrets' },
{ name: 'Integrating With Supabase Auth', url: '/guides/functions/auth' },
{
@@ -862,13 +863,18 @@ export const ai: NavMenuConstant = {
{ name: 'Overview', url: '/guides/ai' },
{ name: 'Concepts', url: '/guides/ai/concepts' },
{
name: 'Structured & unstructured embeddings',
url: '/guides/ai/structured-unstructured-embeddings',
name: 'Structured & unstructured',
url: '/guides/ai/structured-unstructured',
},
{
name: 'Quickstarts',
url: undefined,
items: [{ name: 'Python client', url: '/guides/ai/vecs-python-client' }],
items: [
{ name: 'Developing locally with Vecs', url: '/guides/ai/vecs-python-client' },
{ name: 'Creating and managing collections', url: '/guides/ai/quickstarts/hello-world' },
{ name: 'Text Deduplication', url: '/guides/ai/quickstarts/text-deduplication' },
{ name: 'Face similarity search', url: '/guides/ai/quickstarts/face-similarity' },
],
},
{
name: 'Guides',
+2
View File
@@ -16,6 +16,7 @@ import FunctionsExamples from './FunctionsExamples'
import { Mermaid } from 'mdx-mermaid/lib/Mermaid'
import RefSubLayout from '~/layouts/ref/RefSubLayout'
import { Heading } from './CustomHTMLElements'
import DatabaseSetup from './MDX/database_setup.mdx'
import ProjectSetup from './MDX/project_setup.mdx'
import QuickstartIntro from './MDX/quickstart_intro.mdx'
import SocialProviderSettingsSupabase from './MDX/social_provider_settings_supabase.mdx'
@@ -66,6 +67,7 @@ const components = {
FunctionsExamples,
JwtGenerator,
QuickstartIntro,
DatabaseSetup,
ProjectSetup,
SocialProviderSetup,
SocialProviderSettingsSupabase,
+2 -2
View File
@@ -53,14 +53,14 @@ Compared to our 2-dimensional example above, most embedding models will output m
Why is this useful? Once we have generated embeddings on multiple texts, it is trivial to calculate how similar they are using vector math operations like cosine distance. A common use case for this is search. Your process might look something like this:
1. Pre-process your knowledge base and generate embeddings for each page
2. Store your embeddings to be referenced later (more on this)
2. Store your embeddings to be referenced later
3. Build a search page that prompts your user for input
4. Take user's input, generate a one-time embedding, then perform a similarity search against your pre-processed embeddings.
5. Return the most similar pages to the user
## See also
- [Structured and Unstructured embeddings](/docs/guides/ai/structured-unstructured-embeddings)
- [Structured and Unstructured embeddings](/docs/guides/ai/structured-unstructured)
export const Page = ({ children }) => <Layout meta={meta} children={children} />
@@ -16,7 +16,18 @@ For small workloads it's typical to store your data in a single database.
If you've used [Vecs](/docs/guides/ai/vecs-python-client) to create 3 different collections, you can expose collections to your web or mobile application using [views](/docs/guides/database/tables#views):
![Single Database](/docs/img/ai/scaling/single-database.png)
<div>
<img
alt="single database"
className="dark:hidden"
src="/docs/img/ai/scaling/engineering-for-scale--single-database--light.png"
/>
<img
alt="single database"
className="hidden dark:block"
src="/docs/img/ai/scaling/engineering-for-scale--single-database--dark.png"
/>
</div>
For example, with 3 collections, called `docs`, `posts`, and `images`, we could expose the "docs" inside the public schema like this:
@@ -44,7 +55,18 @@ const { data, error } = await supabase
As you move into production, we recommend running splitting your collections into separate projects. This is because it allows your vector stores to scale independently of your production data. Vectors typically grow faster than operational data, and they have different resource requirements. Running them on separate databases removes the single-point-of-failure.
![With secondaries](/docs/img/ai/scaling/with-secondaries.png)
<div>
<img
alt="With secondaries"
className="dark:hidden"
src="/docs/img/ai/scaling/engineering-for-scale--with-secondaries--light.png"
/>
<img
alt="With secondaries"
className="hidden dark:block"
src="/docs/img/ai/scaling/engineering-for-scale--with-secondaries--dark.png"
/>
</div>
You can use as many secondary databases as you need to manage your collections. With this architecture, you have 2 options for accessing collections within your application:
@@ -120,7 +142,18 @@ const { data, error } = await supabase
This diagram provides an example architecture, allowing you to access the collections either with our client libraries or using Vecs. You can add as many secondary databases as you need, in this example we show one only:
![Multi Database](/docs/img/ai/scaling/multi-database.png)
<div>
<img
alt="multi database"
className="dark:hidden"
src="/docs/img/ai/scaling/engineering-for-scale--multi-database--light.png"
/>
<img
alt="multi database"
className="hidden dark:block"
src="/docs/img/ai/scaling/engineering-for-scale--multi-database--dark.png"
/>
</div>
export const Page = ({ children }) => <Layout meta={meta} children={children} />
+8 -1
View File
@@ -8,7 +8,14 @@ export const meta = {
sidebar_label: 'Google Colab',
}
Google Colab is a hosted Jupyter Notebook service. It provides free access to computing resources, including GPUs and TPUs, and is well-suited to machine learning, data science, and education. We can use Colab to manage collections using [Supabase Vecs](/docs/ai/vecs-python-client).
<a
className="w-64"
href="https://colab.research.google.com/github/supabase/supabase/blob/master/examples/ai/vector_hello_world.ipynb"
>
<img src="/docs/img/ai/colab-badge.svg" />
</a>
Google Colab is a hosted Jupyter Notebook service. It provides free access to computing resources, including GPUs and TPUs, and is well-suited to machine learning, data science, and education. We can use Colab to manage collections using [Supabase Vecs](/docs/guides/ai/vecs-python-client).
In this tutorial we'll connect to a database running on the Supabase [platform](https://app.supabase.com/). If you don't already have a database, you can create one here: [database.new](https://database.new).
@@ -0,0 +1,63 @@
import Layout from '~/layouts/DefaultGuideLayout'
import StepHikeCompact from '~/components/StepHikeCompact'
export const meta = {
id: 'ai-vecs-python-client',
title: 'Face similarity search',
subtitle: 'Identify the celebrities you looks most similar to using Supabase Vecs.',
breadcrumb: 'AI Quickstarts',
}
This guide will walk you through a ["Face Similarity Search"](https://github.com/supabase/supabase/blob/master/examples/ai/face_similarity.ipynb) example using Colab and Supabase Vecs. You'll identify the celebrities you (or any other person) looks most similar to. You will:
1. Launch a Postgres database that uses pgvector to store embeddings
1. Launch a notebook that connects to your database
1. Load the "`ashraq/tmdb-people-image`" celebrity dataset
1. Use the `face_recognition` model to create an embedding for every celebrity photo.
1. Search for similar faces inside the dataset.
<DatabaseSetup />
## Launching a notebook
Launch our [`semantic_text_deduplication`](https://github.com/supabase/supabase/blob/master/examples/ai/face_similarity.ipynb) notebook in Colab:
<a
className="w-64"
href="https://colab.research.google.com/github/supabase/supabase/blob/master/examples/ai/face_similarity.ipynb"
>
<img src="/docs/img/ai/colab-badge.svg" />
</a>
At the top of the notebook, you'll see a button `Copy to Drive`. Click this button to copy the notebook to your Google Drive.
## Connecting to your database
Inside the Notebook, find the cell which specifies the `DB_CONNECTION`. It will contain some code like this:
```python
import vecs
DB_CONNECTION = "postgresql://<user>:<password>@<host>:<port>/<db_name>"
# create vector store client
vx = vecs.create_client(DB_CONNECTION)
```
Replace the `DB_CONNECTION` with your own connection string for your database, which you set up in first step of this guide.
## Stepping through the notebook
Now all that's left is to step through the notebook. You can do this by clicking the "execute" button (`ctrl+enter`) at the top left of each code cell. The notebook guides you through the process of creating a collection, adding data to it, and querying it.
You can view the inserted items in the [Table Editor](https://app.supabase.com/project/_/editor/), by selecting the `vecs` schema from the schema dropdown.
![Colab documents](/docs/img/ai/google-colab/colab-documents.png)
## Next steps
You can now start building your own applications with Vecs. Check our [examples](/docs/guides/ai#examples) for ideas.
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -0,0 +1,63 @@
import Layout from '~/layouts/DefaultGuideLayout'
import StepHikeCompact from '~/components/StepHikeCompact'
export const meta = {
id: 'ai-vecs-python-client',
title: 'Creating and managing collections',
subtitle: 'Connecting to your database with Colab.',
breadcrumb: 'AI Quickstarts',
}
This guide will walk you through a basic ["Hello World"](https://github.com/supabase/supabase/blob/master/examples/ai/vector_hello_world.ipynb) example using Colab and Supabase Vecs. You'll learn how to:
1. Launch a Postgres database that uses pgvector to store embeddings
1. Launch a notebook that connects to your database
1. Create a vector collection
1. Add data to the collection
1. Query the collection
<DatabaseSetup />
## Launching a notebook
Launch our [`vector_hello_world`](https://github.com/supabase/supabase/blob/master/examples/ai/vector_hello_world.ipynb) notebook in Colab:
<a
className="w-64"
href="https://colab.research.google.com/github/supabase/supabase/blob/master/examples/ai/vector_hello_world.ipynb"
>
<img src="/docs/img/ai/colab-badge.svg" />
</a>
At the top of the notebook, you'll see a button `Copy to Drive`. Click this button to copy the notebook to your Google Drive.
## Connecting to your database
Inside the Notebook, find the cell which specifies the `DB_CONNECTION`. It will contain some code like this:
```python
import vecs
DB_CONNECTION = "postgresql://<user>:<password>@<host>:<port>/<db_name>"
# create vector store client
vx = vecs.create_client(DB_CONNECTION)
```
Replace the `DB_CONNECTION` with your own connection string for your database, which you set up in first step of this guide.
## Stepping through the notebook
Now all that's left is to step through the notebook. You can do this by clicking the "execute" button (`ctrl+enter`) at the top left of each code cell. The notebook guides you through the process of creating a collection, adding data to it, and querying it.
You can view the inserted items in the [Table Editor](https://app.supabase.com/project/_/editor/), by selecting the `vecs` schema from the schema dropdown.
![Colab documents](/docs/img/ai/google-colab/colab-documents.png)
## Next steps
You can now start building your own applications with Vecs. Check our [examples](/docs/guides/ai#examples) for ideas.
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -0,0 +1,63 @@
import Layout from '~/layouts/DefaultGuideLayout'
import StepHikeCompact from '~/components/StepHikeCompact'
export const meta = {
id: 'ai-vecs-python-client',
title: 'Semantic Text Deduplication',
subtitle: 'Finding duplicate movie reviews with Supabase Vecs.',
breadcrumb: 'AI Quickstarts',
}
This guide will walk you through a ["Semantic Text Deduplication"](https://github.com/supabase/supabase/blob/master/examples/ai/semantic_text_deduplication.ipynb) example using Colab and Supabase Vecs. You'll learn how to find similar movie reviews using embeddings, and remove any that seem like duplicates. You will:
1. Launch a Postgres database that uses pgvector to store embeddings
1. Launch a notebook that connects to your database
1. Load the IMDB dataset
1. Use the `sentence-transformers/all-MiniLM-L6-v2` model to create an embedding representing the semantic meaning of each review.
1. Search for all duplicates.
<DatabaseSetup />
## Launching a notebook
Launch our [`semantic_text_deduplication`](https://github.com/supabase/supabase/blob/master/examples/ai/semantic_text_deduplication.ipynb) notebook in Colab:
<a
className="w-64"
href="https://colab.research.google.com/github/supabase/supabase/blob/master/examples/ai/semantic_text_deduplication.ipynb"
>
<img src="/docs/img/ai/colab-badge.svg" />
</a>
At the top of the notebook, you'll see a button `Copy to Drive`. Click this button to copy the notebook to your Google Drive.
## Connecting to your database
Inside the Notebook, find the cell which specifies the `DB_CONNECTION`. It will contain some code like this:
```python
import vecs
DB_CONNECTION = "postgresql://<user>:<password>@<host>:<port>/<db_name>"
# create vector store client
vx = vecs.create_client(DB_CONNECTION)
```
Replace the `DB_CONNECTION` with your own connection string for your database, which you set up in first step of this guide.
## Stepping through the notebook
Now all that's left is to step through the notebook. You can do this by clicking the "execute" button (`ctrl+enter`) at the top left of each code cell. The notebook guides you through the process of creating a collection, adding data to it, and querying it.
You can view the inserted items in the [Table Editor](https://app.supabase.com/project/_/editor/), by selecting the `vecs` schema from the schema dropdown.
![Colab documents](/docs/img/ai/google-colab/colab-documents.png)
## Next steps
You can now start building your own applications with Vecs. Check our [examples](/docs/guides/ai#examples) for ideas.
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -1,61 +0,0 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'structured-unstructured-embeddings',
title: 'Structured and unstructured embeddings',
description:
'Supabase is flexible enough to provide structured and unstructured embeddings using pgvector.',
subtitle:
'Supabase is flexible enough to provide structured and unstructured embeddings using pgvector.',
sidebar_label: 'Structured and unstructured embeddings',
}
Most vector stores treat embeddings like NoSQL, unstructured data. Supabase is flexible enough to fit either a structured or an unstructured approach.
Compare these code snippets:
## Structured
```sql
create table docs (
id uuid primary key,
content text,
url string,
embedding vector(1536)
);
insert into docs
(id, content, url, embedding)
values
('79409372-7556-4ccc-ab8f-5786a6cfa4f7', 'Hello world', '/hello-world', [100, 200, 300]);
```
A structured approach is usually defined in SQL, and managed via database [migrations](/docs/guides/getting-started/local-development#database-migrations).
## Unstructured
```py
import vecs
docs = vx.create_collection(name="docs", dimension=1536)
docs.upsert(vectors=[
('79409372-7556-4ccc-ab8f-5786a6cfa4f7', [100, 200, 300], { url: '/hello-world' })
])
```
An unstructured approach is usually defined in Python and has a looser table definition, storing metadata as a json document along with the embedding.
## Choosing the right model
Both approaches create a table where you can store your embeddings and some metadata. You should choose the best approach for your use-case.
- Structured embeddings are typically co-located with some content that is already stored in your database.
- Unstructured embeddings are typically defined at runtime, better-suited for a large body of external content.
Both approaches are fine, and the one you should choose depends on your use-case.
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -0,0 +1,114 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'structured-unstructured-embeddings',
title: 'Structured and Unstructured',
description:
'Supabase is flexible enough to associate structured and unstructured metadata with embeddings.',
subtitle:
'Supabase is flexible enough to associate structured and unstructured metadata with embeddings.',
sidebar_label: 'Structured and unstructured embeddings',
}
Most vector stores treat metadata associated with embeddings like NoSQL, unstructured data. Supabase is flexible enough to store unstructured and structured metadata.
## Structured
```sql
create table docs (
id uuid primary key,
embedding vector(3),
content text,
url string
);
insert into docs
(id, content, url, embedding)
values
('79409372-7556-4ccc-ab8f-5786a6cfa4f7', array[0.1, 0.2, 0.3], 'Hello world', '/hello-world');
```
Notice that we've associated two pieces of metadata, `content` and `url`, with the embedding. Those fields can be filtered, constrained, indexed, and generally operated on using the full power of SQL. Structured metadata fits naturally with a traditional Supabase application, and can be managed via database [migrations](/docs/guides/getting-started/local-development#database-migrations).
## Unstructured
```sql
create table docs (
id uuid primary key,
embedding vector(3),
meta jsonb
);
insert into docs
(id, embedding, meta)
values
(
'79409372-7556-4ccc-ab8f-5786a6cfa4f7',
array[0.1, 0.2, 0.3],
'{"content": "Hello world", "url": "/hello-world"}'
);
```
An unstructured approach does not specify the metadata fields that are expected. It stores all metadata in a flexible `json`/`jsonb` column. The tradeoff is that the querying/filtering capabilities of a schemaless data type are less flexible than when each field has a dedicated column. It also pushes the burden of metadata data integrity onto application code, which is more error prone than enforcing constraints in the database.
The unstructured approach is recommended:
- for ephemeral/interactive workloads e.g. data science or scientific research
- when metadata fields are user-defined or unknown
- during rapid prototyping
Client libraries like python's [vecs](https://github.com/supabase/vecs) use this structure. For example, running:
```py
#!/usr/bin/env python3
import vecs
docs = vx.create_collection(name="docs", dimension=1536)
docs.upsert(vectors=[
('79409372-7556-4ccc-ab8f-5786a6cfa4f7', [100, 200, 300], { url: '/hello-world' })
])
```
automatically creates the unstructured SQL table during the call to `create_collection`.
Note that when working with client libraries that emit SQL DDL, like `create table ...`, you should add that SQL to your migrations when moving to production to maintain a single source of truth for your database's schema.
## Hybrid
The structured metadata style is recommended when the fields being tracked are known in advance. If you have a combination of known and unknown metadata fields, you can accommodate the unknown fields by adding a `json`/`jsonb` column to the table. In that situation, known fields should continue to use dedicated columns for best query performance and throughput.
```sql
create table docs (
id uuid primary key,
embedding vector(3),
content text,
url string,
meta jsonb
);
insert into docs
(id, embedding, meta)
values
(
'79409372-7556-4ccc-ab8f-5786a6cfa4f7',
array[0.1, 0.2, 0.3],
'Hello world',
'/hello-world',
'{"key": "value"}'
);
```
## Choosing the right model
Both approaches create a table where you can store your embeddings and some metadata. You should choose the best approach for your use-case. In summary:
- Structured metadata is best when fields are known in advance or query patterns are predictable e.g. a production Supabase application
- Unstructured metadata is best when fields are unknown/user-defined or when working with data interactively e.g. exploratory research
Both approaches are valid, and the one you should choose depends on your use-case.
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
+11
View File
@@ -74,6 +74,17 @@ Supabase provides a Realtime API using [Realtime](https://github.com/supabase/re
Realtime leverages PostgreSQL's built-in logical replication. You can manage your Realtime API simply by managing Postgres publications.
Go to your project's [Replication section](https://app.supabase.com/project/_/database/replication) to get started.
## API URL and Keys
You can find the API URL and Keys in the [Dashboard](https://app.supabase.com/project/_/settings/api).
<video width="99%" muted playsInline controls={true}>
<source
src="https://xguihxuzqibwxjnimxev.supabase.co/storage/v1/object/public/videos/docs/api/api-url-and-key.mp4"
type="video/mp4"
/>
</video>
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
+1 -1
View File
@@ -95,7 +95,7 @@ Future<void> signOut() async {
## Resources
- [Supabase Account - Free Tier OK](https://supabase.com)
- [Supabase Account - Free Plan OK](https://supabase.com)
- [Supabase JS Client](https://github.com/supabase/supabase-js)
- [Supabase Flutter Client](https://github.com/supabase/supabase-flutter)
@@ -79,6 +79,22 @@ const App = () => (
)
```
### Options
Options are available via 'queryParams':
```<Auth
supabaseClient={supabase}
providers={['google']}
queryParams={{
access_type: 'offline',
prompt: 'consent',
hd: 'domain.com'
}}
onlyThirdPartyProviders={true}
/>
```
### Supported Views
The Auth component is currently shipped with the following views:
@@ -521,6 +521,14 @@ export default function Home() {
> check out [this repo](https://github.com/supabase/supabase/tree/master/examples/auth/nextjs) for more examples, including [realtime subscriptions](https://github.com/supabase/supabase/tree/master/examples/auth/nextjs/app/realtime-posts.tsx).
#### Singleton
The `createClientComponentClient` function implements a [Singleton pattern](https://en.wikipedia.org/wiki/Singleton_pattern) to simplify instantiating Supabase clients. If you need multiple Supabase instances across Client Components - for example, when using multiple schemas - you can pass an additional configuration option for `{ isSingleton: false }` to get a new client every time this function is called.
```jsx
const supabase = createClientComponentClient({ isSingleton: false })
```
### Server Component
[Server Components](https://nextjs.org/docs/getting-started/react-essentials#server-components) allow for asynchronous data to be fetched server-side.
@@ -736,7 +744,7 @@ With v0.7.x of the Next.js Auth Helpers a new naming convention has been impleme
#### createClientComponentClient returns singleton
You no longer need to implement logic to ensure there is only a single instance of the Supabase Client shared across all Client Components - this is now handled by the `createClientComponentClient` function. Call it as many times as you want!
You no longer need to implement logic to ensure there is only a single instance of the Supabase Client shared across all Client Components - this is now the default and handled by the `createClientComponentClient` function. Call it as many times as you want!
```jsx
"use client";
@@ -749,6 +757,8 @@ export default function() {
}
```
For an example of creating multiple Supabase clients, check [Singleton section](/docs/guides/auth/auth-helpers/nextjs#singleton) above.
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -88,7 +88,7 @@ Future<void> signOut() async {
## Resources
- [Supabase Account - Free Tier OK](https://supabase.com)
- [Supabase Account - Free Plan OK](https://supabase.com)
- [Supabase JS Client](https://github.com/supabase/supabase-js)
- [Supabase Flutter Client](https://github.com/supabase/supabase-flutter)
@@ -65,7 +65,7 @@ We are using `next` as our query parameter, but this can name whatever you like.
The email link you receive will behave like a magic link. When the link is clicked you will be sent to the `redirectTo` URL you specified that points to the path with the exchange code.
### Exchange authorization code
After redirecting to the server page, we need to retrieve the code from the query parameter called `code` and pass it to the `.exchangeAuthCodeForSession` function.
After redirecting to the server page, we need to retrieve the code from the query parameter called `code` and pass it to the `.exchangeCodeForSession` function.
```ts
// api/auth/callback.ts
@@ -1,4 +1,5 @@
import Layout from '~/layouts/DefaultGuideLayout'
import AppleSecretGenerator from '~/components/AppleSecretGenerator'
export const meta = {
id: 'auth-apple',
@@ -89,69 +90,15 @@ Now you'll need to download a `secret key` file from Apple that will be used to
- Save the downloaded file -- this contains your "secret key" that will be used to generate your `client_secret`.
- Click `Done` at the top right.
## Generate a `client_secret`
## Generate a client secret
The `secret key` you downloaded is used to create the `client_secret` string you'll need to authenticate your users.
You need to configure a client secret when using Sign in with Apple for Web. This is a specially crafted [JWT signed with a secret key downloaded from Apple's Developer Center](https://developer.apple.com/documentation/signinwithapplerestapi/generate_and_validate_tokens).
According to the [Apple Docs](https://developer.apple.com/documentation/signinwithapplerestapi/generate_and_validate_tokens) it needs to be a JWT
token encrypted using the Elliptic Curve Digital Signature Algorithm (ECDSA) with the P-256 curve and the SHA-256 hash algorithm.
<Admonition>
Use this tool to generate a new Apple client secret. No keys leave your browser!
</Admonition>
At this time, the easiest way to generate this JWT token is with [Ruby](https://www.ruby-lang.org/en/).
If you don't have Ruby installed, you can [Download Ruby Here](https://www.ruby-lang.org/en/downloads).
- Install Ruby (or check to make sure it's installed on your system).
- Install [ruby-jwt](https://github.com/jwt/ruby-jwt).
- From the command line, run: `sudo gem install jwt`.
Create the script below using a text editor: `secret_gen.rb`
```ruby
require "jwt"
key_file = "Path to the private key"
team_id = "Your Team ID"
client_id = "The Service ID of the service you created"
key_id = "The Key ID of the private key"
validity_period = 180 # In days. Max 180 (6 months) according to Apple docs.
private_key = OpenSSL::PKey::EC.new IO.read key_file
token = JWT.encode(
{
iss: team_id,
iat: Time.now.to_i,
exp: Time.now.to_i + 86400 * validity_period,
aud: "https://appleid.apple.com",
sub: client_id
},
private_key,
"ES256",
header_fields=
{
kid: key_id
}
)
puts token
```
1. Edit the `secret_gen.rb` file:
- `key_file` = "Path to the private key you downloaded from Apple". It should look like this: `AuthKey_XXXXXXXXXX.p8`.
- `team_id` = "Your Team ID". This is found at the Apple Developer website, under Membership details. This is a 10-character alphanumeric string called "Team ID". Alternatively, this can be seen next to your name in the upper right when viewing your Certificates, Identifiers & Profiles.
- `client_id` = "The Service ID of the service you created". This is the `Services ID` you created in the above step `Obtain a Services ID`. If you've lost this ID, you can find it in the Apple Developer Site:
- Go to `Certificates, Identifiers & Profiles`.
- Click `Identifiers` at the left.
- At the top right drop-down, select `Services IDs`.
- Find your Identifier in the list (i.e. app.com.acme.roadrunner).
- `key_id` = "The Key ID of the private key". This can be found in the name of your downloaded secret file (For a file named `AuthKey_XXXXXXXXXX.p8` your key_id is `XXXXXXXXXX`). If you've lost this ID, you can find it in the Apple Developer Site:
- Go to `Certificates, Identifiers & Profiles`.
- Click `Keys` at the left.
- Click on your newly-created key in the list.
- Look under `Key ID` to find your key_id.
2. From the command line, run: `ruby secret_gen.rb > client_secret.txt`.
3. Your `client_secret` is now stored in this `client_secret.txt` file.
<AppleSecretGenerator />
## Add your OAuth credentials to Supabase
@@ -180,8 +127,6 @@ async function signout() {
## Resources
- [Apple Developer Account](https://developer.apple.com).
- [Ruby](https://www.ruby-lang.org/en/) Docs.
- [ruby-jwt](https://github.com/jwt/ruby-jwt) library.
- Thanks to [Janak Amarasena](https://medium.com/@janakda) who did all the heavy lifting in [How to configure Sign In with Apple](https://medium.com/identity-beyond-borders/how-to-configure-sign-in-with-apple-77c61e336003).
export const Page = ({ children }) => <Layout meta={meta} children={children} />
@@ -68,7 +68,7 @@ async function signout() {
## Resources
- [Supabase Account - Free Tier OK](https://supabase.com)
- [Supabase Account - Free Plan OK](https://supabase.com)
- [Supabase JS Client](https://github.com/supabase/supabase-js)
- [Bitbucket Account](https://bitbucket.org)
@@ -69,7 +69,7 @@ async function signout() {
## Resources
- [Supabase Account - Free Tier OK](https://supabase.com)
- [Supabase Account - Free Plan OK](https://supabase.com)
- [Supabase JS Client](https://github.com/supabase/supabase-js)
- [Discord Account](https://discord.com)
- [Discord Developer Portal](https://discord.com/developers)
@@ -83,7 +83,7 @@ async function signout() {
## Resources
- [Supabase Account - Free Tier OK](https://supabase.com)
- [Supabase Account - Free Plan OK](https://supabase.com)
- [Supabase JS Client](https://github.com/supabase/supabase-js)
- [Facebook Developers Dashboard](https://developers.facebook.com/)
@@ -79,7 +79,7 @@ async function signout() {
## Resources
- [Supabase Account - Free Tier OK](https://supabase.com)
- [Supabase Account - Free Plan OK](https://supabase.com)
- [Supabase JS Client](https://github.com/supabase/supabase-js)
- [GitHub Developer Settings](https://github.com/settings/developers)
@@ -65,7 +65,7 @@ async function signout() {
## Resources
- [Supabase Account - Free Tier OK](https://supabase.com)
- [Supabase Account - Free Plan OK](https://supabase.com)
- [Supabase JS Client](https://github.com/supabase/supabase-js)
- [GitLab Account](https://gitlab.com)
@@ -105,6 +105,7 @@ async function signInWithGoogle() {
queryParams: {
access_type: 'offline',
prompt: 'consent',
hd: 'domain.com //google will also allowo OAuth logins to be restricted to a specified domain using the 'hd' parameter
},
},
})
@@ -113,7 +114,7 @@ async function signInWithGoogle() {
## Resources
- [Supabase Account - Free Tier OK](https://supabase.com)
- [Supabase Account - Free Plan OK](https://supabase.com)
- [Supabase JS Client](https://github.com/supabase/supabase-js)
- [Google Cloud Platform Console](https://console.cloud.google.com/home/dashboard)
@@ -64,7 +64,7 @@ async function signout() {
## Resources
- [Supabase Account - Free Tier OK](https://supabase.com)
- [Supabase Account - Free Plan OK](https://supabase.com)
- [Supabase JS Client](https://github.com/supabase/supabase-js)
- [LinkedIn Developer Dashboard](https://api.LinkedIn.com/apps)
@@ -68,7 +68,7 @@ async function signout() {
## Resources
- [Supabase Account - Free Tier OK](https://supabase.com)
- [Supabase Account - Free Plan OK](https://supabase.com)
- [Supabase JS Client](https://github.com/supabase/supabase-js)
- [Notion Account](https://notion.so)
- [Notion Developer Portal](https://www.notion.so/my-integrations)
@@ -80,7 +80,7 @@ async function signout() {
## Resources
- [Supabase Account - Free Tier OK](https://supabase.com)
- [Supabase Account - Free Plan OK](https://supabase.com)
- [Supabase JS Client](https://github.com/supabase/supabase-js)
- [Slack Developer Dashboard](https://api.slack.com/apps)
@@ -72,7 +72,7 @@ async function signout() {
## Resources
- [Supabase Account - Free Tier OK](https://supabase.com)
- [Supabase Account - Free Plan OK](https://supabase.com)
- [Supabase JS Client](https://github.com/supabase/supabase-js)
- [Spotify Developer Dashboard](https://developer.spotify.com/dashboard/)
@@ -83,7 +83,7 @@ async function signout() {
## Resources
- [Supabase Account - Free Tier OK](https://supabase.com)
- [Supabase Account - Free Plan OK](https://supabase.com)
- [Supabase JS Client](https://github.com/supabase/supabase-js)
- [Twitch Account](https://twitch.tv)
- [Twitch Developer Console](https://dev.twitch.tv/console)
@@ -73,7 +73,7 @@ async function signout() {
## Resources
- [Supabase Account - Free Tier OK](https://supabase.com)
- [Supabase Account - Free Plan OK](https://supabase.com)
- [Supabase JS Client](https://github.com/supabase/supabase-js)
- [Twitter Developer Dashboard](https://developer.twitter.com/en/portal/dashboard)
@@ -81,7 +81,7 @@ async function signout() {
## Resources
- [Supabase Account - Free Tier OK](https://supabase.com)
- [Supabase Account - Free Plan OK](https://supabase.com)
- [Supabase JS Client](https://github.com/supabase/supabase-js)
- [Zoom App Marketplace](https://marketplace.zoom.us/)
@@ -25,7 +25,7 @@ You can use the `supabase sso` [subcommands](/docs/reference/cli/supabase-sso) t
SAML 2.0 support is disabled by default on Supabase projects. You can configure this on the [Auth Providers](https://app.supabase.com/project/_/auth/providers) page on your project.
Please note that SAML 2.0 support is offered on tiers Pro and above. Check the [Pricing](https://supabase.com/pricing) page for more information.
Please note that SAML 2.0 support is offered on plans Pro and above. Check the [Pricing](https://supabase.com/pricing) page for more information.
## Terminology
+1 -1
View File
@@ -160,7 +160,7 @@ returns:
## Resources
- [Supabase JS Client](https://github.com/supabase/supabase-js)
- [Supabase Account - Free Tier OK](https://supabase.com)
- [Supabase Account - Free Plan OK](https://supabase.com)
- [PostgreSQL Arrays](https://www.postgresql.org/docs/15/arrays.html)
export const Page = ({ children }) => <Layout meta={meta} children={children} />
@@ -3,20 +3,17 @@ import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'column-encryption',
title: 'Column Encryption',
description: 'Use Supabase to store and serve files.',
description: 'Encrypt columns using Transparent Column Encryption.',
subtitle: 'Encrypt columns using Transparent Column Encryption.',
sidebar_label: 'Overview',
video: 'https://www.youtube.com/v/J9mTPY8rIXE',
}
Encrypted Columns for Tables
Supabase provides a secure method for encrypting columns using [Vault](/docs/guides/database/vault), our Postgres secrets manager. Vault is Postgres extension with an integrated UI intended to act as a secure global secrets management for you project.
## Transparent Column Encryption (TCE)
Vault enables an advanced feature called Transparent Column Encryption (TCE) which provides a safe way to encrypt your data so that it doesn't leak into logs and backups. It can also provide row-level authenticated encryption.
TCE provides a safe way to encrypt your data so that it doesn't leak into logs and backups. It can also provide row-level authenticated encryption.
TCE is the primary building block of [Vault](/docs/guides/database/vault), Supabase's Postgres secrets manager. Vault is a built-in table with an integrated UI intended to act as a secure global secrets management for you project. However if you need more fine-grain control over your encrypted data, such as encrypting columns in your own tables, you can use TCE directly. Any Postgres value that can be cast to `text` or `bytea` can be encrypted using TCE.
### Encrypting columns
## Encrypting columns
When creating a new column in the Dashboard, you can choose to encrypt a `text` or `bytea` column. You will choose which key you would like to encrypt it with by selecting an existing key ID or creating a new one.
@@ -26,10 +23,50 @@ Once you've created an encrypted column, you can insert data into the table like
![Encrypted data](/docs/img/guides/database/vault-encrypted-data.png)
## Decrypting data
Decrypted data is accessed using a special view that is automatically created after adding an encrypted column to a table. This view decrypts the data row-by-row as you access it. By default, this view is called `decrypted_<your-table-name>`. In the example below, the decryption view for the `profiles` table is called `decrypted_profiles`. Notice there is a new column in the view called `decrypted_emails` that contains the decrypted email value.
![Decrypted data](/docs/img/guides/database/vault-decrypted-data.png)
## Using an Encrypted Table
Now that you have TCE setup for a table, it's easy to use by simply inserting data into the table, and querying that data by looking at its generated view. The view is named `decrypted_<table_name>` and by default is in the same schema as your table:
```sql
insert into secrets
(secret, account_id)
values
('1234-5678-8765-4321', 123);
```
Now that you have inserted data, look at the table and notice how the secret is encrypted. This is the data that is stored on disk, the encrypted card number, the key id, and the account id, **but the key itself is not stored**. This means if someone gets a backup or dump of your database, they cannot decrypt the secret, they do not have the key, only the key ID:
```sql
> select * from secrets where account_id = 123;
-[ RECORD 1 ]------+---------------------------------------------------------------------
id | 1
secret | jf8KfImkKTr+j4gzyDZQtLDEFL9eSlFuKjNlNEJvDg+OIKUr2wjF/8NnYcLisb5F9xiN
account_id | 123
key_id | 7f753c4f-8c68-457a-8801-1798b2e9f44d
nonce | \x300a14aa721184ff7cf0f6bf088da267
```
For you, the developer, you need the unencrypted secret for you application. No problem, you can access that data using the dynamically generated decryption view `decrypted_secrets`:
```sql
> select * from decrypted_secrets where account_id = 123;
-[ RECORD 1 ]----------------+---------------------------------------------------------------------
id | 1
secret | jf8KfImkKTr+j4gzyDZQtLDEFL9eSlFuKjNlNEJvDg+OIKUr2wjF/8NnYcLisb5F9xiN
decrypted_secret | 1234-5678-8765-4321
account_id | 123
key_id | 7f753c4f-8c68-457a-8801-1798b2e9f44d
nonce | \x300a14aa721184ff7cf0f6bf088da267
```
Notice how there is a new column called `decrypted_secret`. This column is not stored in database or on disk at all, it is generated “on-the-fly” as you select from the view. Database dumps do not contain this information, only the view itself, and most importantly, **raw decryption keys are never stored**.
## How Key Derivation Works
The current state-of-the-art in encryption libraries is [libsodium](https://doc.libsodium.org/).
@@ -202,43 +239,7 @@ security label for pgsodium
The new label indicates which column is to be associated with the secret, and that's it! Your `account_id` and secret are now protected under the same authentication signature as the secret itself.
## Using an Encrypted Table
Now that you have TCE setup for a table, it's easy to use by simply inserting data into the table, and querying that data by looking at its generated view. The view is named `decrypted_<table_name>` and by default is in the same schema as your table:
```sql
insert into secrets
(secret, account_id)
values
('1234-5678-8765-4321', 123);
```
Now that you have inserted data, look at the table and notice how the secret is encrypted. This is the data that is stored on disk, the encrypted card number, the key id, and the account id, **but the key itself is not stored**. This means if someone gets a backup or dump of your database, they cannot decrypt the secret, they do not have the key, only the key ID:
```sql
> select * from secrets where account_id = 123;
-[ RECORD 1 ]------+---------------------------------------------------------------------
id | 1
secret | jf8KfImkKTr+j4gzyDZQtLDEFL9eSlFuKjNlNEJvDg+OIKUr2wjF/8NnYcLisb5F9xiN
account_id | 123
key_id | 7f753c4f-8c68-457a-8801-1798b2e9f44d
nonce | \x300a14aa721184ff7cf0f6bf088da267
```
For you, the developer, you need the unencrypted secret for you application. No problem, you can access that data using the dynamically generated decryption view `decrypted_secrets`:
```sql
> select * from decrypted_secrets where account_id = 123;
-[ RECORD 1 ]----------------+---------------------------------------------------------------------
id | 1
secret | jf8KfImkKTr+j4gzyDZQtLDEFL9eSlFuKjNlNEJvDg+OIKUr2wjF/8NnYcLisb5F9xiN
decrypted_secret | 1234-5678-8765-4321
account_id | 123
key_id | 7f753c4f-8c68-457a-8801-1798b2e9f44d
nonce | \x300a14aa721184ff7cf0f6bf088da267
```
Notice how there is a new column called `decrypted_secret`. This column is not stored in database or on disk at all, it is generated “on-the-fly” as you select from the view. Database dumps do not contain this information, only the view itself, and most importantly, **raw decryption keys are never stored**.
## Resources
- [Supabase Vault](/docs/guides/database/vault)
- Read more about Supabase Vault in the [blog post](https://supabase.com/blog/vault-now-in-beta)
@@ -1,4 +1,5 @@
import Layout from '~/layouts/DefaultGuideLayout'
import StepHikeCompact from '~/components/StepHikeCompact'
export const meta = {
id: 'connecting-to-postgres',
@@ -8,54 +9,21 @@ export const meta = {
Supabase provides several options for programmatically connecting to your Postgres database:
## Types of Connection
1. Direct connections using Postgres' standard connection system
2. Connection pooling using PgBouncer
3. Programmatic access uing the [Serverless APIs](/docs/guides/api)
- HTTP connections using the API.
- Direct connections using Postgres' standard connection system.
- Connection pooling using PgBouncer.
## Serverless APIs
### Direct vs Pooling vs API
- A "direct connection" is when a connection is made to the database using Postgres' native connection implementation. You should use this for tools which are always alive - usually installed on a long-running server.
- A "connection pool" is a system (external to Postgres) which keeps connections "open". You should use this for serverless functions and tools which disconnect from the database frequently.
- The API is an auto-generated REST interface. You should use this for all browser and application interactions. The API server internally handles a connection pool.
Why would you use a connection pool? Primarily because the way that Postgres handles connections isn't very scalable for a large number of _temporary_ connections.
You can use these simple questions to determine which connection method to use:
- Are you connecting to a database and _maintaining_ a connection? If yes, use a direct connection.
- Are you connecting to your database and then _disconnecting_ immediately (e.g. a serverless environment)? If yes, use a connection pool.
## API
Supabase provides an auto-updating [API](/docs/guides/database/api). This is the easiest way to get started if you are managing data (fetching, inserting, updating).
### Interfaces
We provides several types of API to suit your preferences and use-case:
Supabase provides auto-updating [APIs](/docs/guides/database/api). This is the easiest way to get started if you are managing data (fetching, inserting, updating). We provides several types of API to suit your preferences:
- [REST](/docs/guides/database/api#rest-api): interact with your database through a REST interface.
- [GraphQL](/docs/guides/database/api#graphql-api): interact with your database through a GraphQL interface.
- [Realtime](/docs/guides/database/api#realtime-api): listen to database changes over websockets.
You cannot manage the database schema via the API (for security reasons). To do that you can use the dashboard or connect directly to your database.
### API URL and Keys
You can find the API URL and Keys in the [Dashboard](https://app.supabase.com/project/_/settings/api).
<video width="99%" muted playsInline controls={true}>
<source
src="https://xguihxuzqibwxjnimxev.supabase.co/storage/v1/object/public/videos/docs/api/api-url-and-key.mp4"
type="video/mp4"
/>
</video>
## Direct connections
Every Supabase project provides a full Postgres database. You can connect to the database using any tool which supports Postgres.
### Finding your connection string
Every Supabase project provides a full Postgres database. You can connect to the database using [any tool which supports Postgres](#integrations). You can find the connection string in the [Database settings](https://app.supabase.com/project/_/settings/database) inside the dashboard:
1. Go to the `Settings` section.
2. Click `Database`.
@@ -68,42 +36,9 @@ Every Supabase project provides a full Postgres database. You can connect to the
/>
</video>
## Connection Pool
## Connection Pooler
Connection pools are useful for managing a large number of _temporary_ connections. For example, if you are using [Prisma](/docs/guides/integrations/prisma) deployed to a Serverless environment.
### How connection pooling works
A "connection pool" is a system (external to Postgres) which manages connections, rather than PostgreSQL's native system. Supabase uses [PgBouncer](https://www.pgbouncer.org/) for connection pooling.
When a client makes a request, PgBouncer "allocates" an available connection to the client.
When the client transaction or session is completed the connection is returned to the pool and is free to be used by another client.
![Connection pooling](/docs/img/guides/database/connection-pool.png)
### Pool modes
Pool Mode determines how PgBouncer handles a connection.
#### Session
When a new client connects, a connection is assigned to the client until it disconnects. Afterward, the connection is returned back to the pool.
All PostgreSQL features can be used with this option.
#### Transaction
This is the suggested option for serverless functions. A connection is only assigned to the client for the duration of a transaction. Two consecutive transactions from the same client
could be executed over two different connections.
Some session-based PostgreSQL features such as prepared statements are not available with this option.
A comprehensive list of incompatible features can be found [here](https://www.pgbouncer.org/features.html).
#### Statement
This is the most granular option. Connections are returned to the pool after every statement. Transactions with multiple statements are not allowed. This is best used when `AUTOCOMMIT` is in use.
### Finding the connection pool config
Every Supabase project comes with PgBouncer for connection pooling. A connection pooler is useful for managing a large number of _temporary_ connections. For example, if you are using [Prisma](/docs/guides/integrations/prisma), Drizzle, Kysely, or anything deployed to a Serverless environment (AWS Lambdas or Edge Functions). You can find the connection pool config in the [Database settings](https://app.supabase.com/project/_/settings/database) inside the dashboard:
1. Go to the `Settings` section.
2. Click `Database`.
@@ -116,34 +51,134 @@ This is the most granular option. Connections are returned to the pool after eve
/>
</video>
## Choosing a connection method
- The Serverless APIs provide programmatic access and have built-in connection pooling. You can use these for all browser and application interactions. We recommend using these wherever possible.
- A "direct connection" is Postgres' native connection system. You should use this for tools which are always alive - usually installed on a long-running server, like Node.js, Ruby, Python, etc.
- A "connection pooler" is a tool which keeps connections "alive". You should use this for serverless functions and tools which disconnect from the database frequently, like Prisma, Drizzle, Kysely, etc.
Why would you use a connection pool? Primarily because the way that Postgres handles connections isn't very scalable for a large number of _temporary_ connections. You can use these simple questions to determine which connection method to use:
- Are you connecting to a database and _maintaining_ a connection? If yes, use a direct connection.
- Are you connecting to your database and then _disconnecting_ immediately (e.g. a serverless environment)? If yes, use a connection pool.
## Connecting with SSL
Use this when connecting to your database to prevent snooping and man-in-the-middle attacks.
You should connect to your database using SSL wherever possible, to prevent snooping and man-in-the-middle attacks.
You can obtain your connection info and Server root certificate from your application's dashboard:
Obtain your connection info and Server root certificate from your application’s dashboard.
![Connection Info and Certificate.](/docs/img/guides/database/connection-info-cert.png)
Assuming you’ve downloaded your certificate and it’s located at `$HOME/Downloads/prod-ca-2021.cer`, and your Host address is `db.abcdefghijklm.supabase.co` you can connect to the DB with
SSL enabled as illustrated below:
## How connection pooling works
1. With `psql`
A "connection pool" is a system (external to Postgres) which manages connections, rather than PostgreSQL's native system. Supabase uses [PgBouncer](https://www.pgbouncer.org/) for connection pooling.
```
psql "sslmode=verify-full sslrootcert=$HOME/Downloads/prod-ca-2021.cer host=db.abcdefghijklm.supabase.co dbname=postgres user=postgres"
When a client makes a request, PgBouncer "allocates" an available connection to the client. When the client transaction or session is completed the connection is returned to the pool and is free to be used by another client.
![Connection pooling](/docs/img/guides/database/connection-pool.png)
Pgbounce provides several Pool Modes, each handling connections differently:
#### Session
When a new client connects, a connection is assigned to the client until it disconnects. Afterward, the connection is returned back to the pool.
All PostgreSQL features can be used with this option.
#### Transaction
This is the suggested option for serverless functions. A connection is only assigned to the client for the duration of a transaction. Two consecutive transactions from the same client could be executed over two different connections.
Some session-based PostgreSQL features such as prepared statements are not available with this option. A comprehensive list of incompatible features can be found [here](https://www.pgbouncer.org/features.html).
#### Statement
This is the most granular option. Connections are returned to the pool after every statement. Transactions with multiple statements are not allowed. This is best used when `AUTOCOMMIT` is in use.
## Integrations
### Connecting with psql
[`psql`](https://www.postgresql.org/docs/current/app-psql.html) is a command-line tool that comes with Postgres.
Assuming you've downloaded your SSL certificate to `$HOME/Downloads/prod-supabase.cer`, and your host address is `db.ref.supabase.co` you connect to your database via SSL:
```shell
psql "sslmode=verify-full sslrootcert=$HOME/Downloads/prod-supabase.cer host=db.ref.supabase.co dbname=postgres user=postgres"
```
2. With `pgAdmin`
a. Register a new Postgres server
![Register a new postgres server.](/docs/img/guides/database/register-server-pgAdmin.png)
### Connecting with pgAdmin
b. Name your server to your liking and add the connection info.
![Name Postgres Server.](/docs/img/guides/database/name-pg-server.png)
![Add Connection Info.](/docs/img/guides/database/add-pg-server-conn-info.png)
[`pgAdmin`](https://www.pgadmin.org/) is a GUI tool for managing Postgres databases. You can use it to connect to your database via SSL:
3. Navigate to the SSL tab and change the SSL mode to Require. Next navigate to the Root certificate input, it will open up a
file-picker modal. Select the certificate you downloaded from your Supabase dashboard and save the server details. PgAdmin
should now be able to connect to your Postgres via SSL.
![Add Connection Info.](/docs/img/guides/database/add-ssl-config.png)
<StepHikeCompact>
<StepHikeCompact.Step step={1}>
<StepHikeCompact.Details title="Register">
Register a new Postgres server.
</StepHikeCompact.Details>
<StepHikeCompact.Code>
![Register a new postgres server.](/docs/img/guides/database/register-server-pgAdmin.png)
</StepHikeCompact.Code>
</StepHikeCompact.Step>
<StepHikeCompact.Step step={2}>
<StepHikeCompact.Details title="Name">
Name your server.
</StepHikeCompact.Details>
<StepHikeCompact.Code>
![Name Postgres Server.](/docs/img/guides/database/name-pg-server.png)
</StepHikeCompact.Code>
</StepHikeCompact.Step>
<StepHikeCompact.Step step={3}>
<StepHikeCompact.Details title="Connect">
Add the connection info. You can use the "Direct connection" config, which you can find in your Supabase dashboard.
</StepHikeCompact.Details>
<StepHikeCompact.Code>
![Add Connection Info.](/docs/img/guides/database/add-pg-server-conn-info.png)
</StepHikeCompact.Code>
</StepHikeCompact.Step>
<StepHikeCompact.Step step={4}>
<StepHikeCompact.Details title="SSL">
Navigate to the SSL tab and change the SSL mode to Require. Next navigate to the Root certificate input, it will open up a file-picker modal. Select the certificate you downloaded from your Supabase dashboard and save the server details. PgAdmin should now be able to connect to your Postgres via SSL.
</StepHikeCompact.Details>
<StepHikeCompact.Code>
![Add Connection Info.](/docs/img/guides/database/add-ssl-config.png)
</StepHikeCompact.Code>
</StepHikeCompact.Step>
</StepHikeCompact>
export const Page = ({ children }) => <Layout meta={meta} children={children} />
@@ -79,7 +79,7 @@ const { data, error } = await supabase
## Resources
- [Supabase Account - Free Tier OK](https://supabase.com)
- [Supabase Account - Free Plan OK](https://supabase.com)
- [Postgrest Operators](https://postgrest.org/en/stable/api.html#operators)
- [Supabase API: JavaScript select](/docs/reference/javascript/select)
- [Supabase API: JavaScript modifiers](/docs/reference/javascript/using-modifiers)
+117 -70
View File
@@ -3,19 +3,15 @@ import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'vault',
title: 'Vault',
description: 'Use Supabase to store and serve files.',
description:
'Vault is a Postgres extension and accompanying Supabase UI that makes it safe and easy to store encrypted secrets.',
subtitle: 'Managing secrets in Postgres.',
sidebar_label: 'Overview',
video: 'https://www.youtube.com/v/J9mTPY8rIXE',
}
Supabase Vault provides encrypted secret storage and Encrypted Columns for Tables.
Vault is a Postgres extension and accompanying Supabase UI that makes it safe and easy to store encrypted secrets and other data in your database. This opens up a lot of possibilities to use Postgres in ways that go beyond what is available in a stock distribution.
From a product perspective, Supabase groups a number of related features under the “Vault banner”. Let's explore a few of these features.
## Secrets Management
Under the hood, the Vault is a table of Secrets and Encryption Keys that are stored using [Authenticated Encryption](https://en.wikipedia.org/wiki/Authenticated_encryption) on disk. They are then available in decrypted form through a Postgres view so that the secrets can be used by applications from SQL. Because the secrets are stored on disk encrypted and authenticated, any backups or replication streams also preserve this encryption in a way that can't be decrypted or forged.
Supabase provides a dashboard UI for the Vault that makes storing secrets easy. Click a button, type in your secret, and save. Optionally create your own keys you can use to encrypt your secret. Your secret will then be stored on disk encrypted using the specified key.
@@ -31,79 +27,48 @@ Supabase provides a dashboard UI for the Vault that makes storing secrets easy.
There are two main parts to the Vault UI, Secrets and Encryption Keys:
- **Secrets:** Use the Vault to store Secrets - everything from Environment Variables to API Keys. You can use these Secrets anywhere in your database: Postgres [Functions](/docs/guides/database/functions), Triggers, and [Webhooks](/docs/guides/database/webhooks). From a SQL perspective, accessing secrets is as easy as querying a table (or in this case, a view). The underlying secrets tables will be stored in encrypted form.
- **Encryption Keys:** These are keys used to encrypt data inside your database. You can create different Encryption Keys for different purposes, for example: one for encrypting user-data, and another for application-data. Each key is encrypted itself using a root encryption key that lives outside of the database. See **[Encryption key location](#encryption-key-location)** for more details.
## Secrets
## Deep Dive on How The Vault works
You can use the Vault to store secrets - everything from Environment Variables to API Keys. You can then use these secrets anywhere in your database: Postgres [Functions](/docs/guides/database/functions), Triggers, and [Webhooks](/docs/guides/database/webhooks). From a SQL perspective, accessing secrets is as easy as querying a table (or in this case, a view). The underlying secrets tables will be stored in encrypted form.
<div className="video-container">
<iframe
src="https://www.youtube-nocookie.com/embed/QHLPNDrdN2w"
title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen
></iframe>
</div>
## Encryption Keys
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.
### Authenticated Encryption with Associated Data
The first important feature of TCE is that it uses an [Authenticated Encryption with Associated Data](<https://en.wikipedia.org/wiki/Authenticated_encryption#Authenticated_encryption_with_associated_data_(AEAD)>) encryption algorithm (based on libsodium).
### Encryption key location
**Authenticated Encryption** means that in addition to the data being encrypted, it is also signed so that it cannot be forged. You can guarantee that the data was encrypted by someone you trust, which you wouldn't get with encryption alone. The decryption function verifies that the signature is valid _before decrypting the value_.
**Associated Data** means that you can include any other columns from the same row as part of the signature computation. This doesn't encrypt those other columns - rather it ensures that your encrypted value is only associated with columns from that row. If an attacker were to copy an encrypted value from another row to the current one, the signature would be rejected (assuming you used a unique column in the associated data).
Another important feature of pgsodium is that the encryption keys are never stored in the database alongside the encrypted data. Instead, only a **Key ID** is stored, which is a reference to the key that is only accessible outside of SQL. Even if an attacker can capture a dump of your entire database, they will see only encrypted data and key IDs, _never the raw key itself_.
This is an important safety precaution - there is little value in storing the encryption key in the database itself as this would be like locking your front door but leaving the key in the lock! Storing the key outside the database fixes this issue.
Where are the keys stored? Supabase creates and manages the root keys (from which all key IDs are derived) in our secured backend systems. We keep this root key safe and separate from your data. You remain in control of your keys - a separate API endpoint is available that you can use to access the key if you want to decrypt your data outside of Supabase.
These are keys used to encrypt data inside your database. You can create different Encryption Keys for different purposes, for example: one for encrypting user-data, and another for application-data. Each key is encrypted itself using a root encryption key that lives outside of the database. See **[Encryption key location](#encryption-key-location)** for more details.
## Using the Vault
Using the vault is as simple as `INSERT`ing data into the
`vault.secret` table.
You can manage secrets and encryption keys from the UI or using SQL.
### Adding secrets
There is also a handy function for creating secrets called `vault.create_secret()`:
```sql
postgres=> insert into vault.secrets (secret) values ('s3kre3t_k3y') returning *;
-[ RECORD 1 ]-------------------------------------------------------------
id | d91596b8-1047-446c-b9c0-66d98af6d001
name |
description |
secret | S02eXS9BBY+kE3r621IS8beAytEEtj+dDHjs9/0AoMy7HTbog+ylxcS22A==
key_id | 7f5ad44b-6bd5-4c99-9f68-4b6c7486f927
nonce | \x3aa2e92f9808e496aa4163a59304b895
created_at | 2022-12-14 02:29:21.3625+00
updated_at | 2022-12-14 02:29:21.3625+00
```
There is also a handy function for creating secrets called
`vault.create_secret()`:
```sql
postgres=> select vault.create_secret('another_s3kre3t');
-[ RECORD 1 ]-+-------------------------------------
create_secret | c9b00867-ca8b-44fc-a81d-d20b8169be17
select vault.create_secret('my_s3kre3t');
```
The function returns the UUID of the new secret.
## Name and Description
Secrets can also have an optional _unique_ name, or an optional description. These are also arguments to `vault.create_secret()`:
<details>
<summary>Show Result</summary>
```sql
postgres=> select vault.create_secret('another_s3kre3t', 'unique_name', 'This is the description');
-[ RECORD 1 ]-+-------------------------------------
create_secret | 7095d222-efe5-4cd5-b5c6-5755b451e223
create_secret | c9b00867-ca8b-44fc-a81d-d20b8169be17
```
postgres=> select * from vault.secrets where id = '7095d222-efe5-4cd5-b5c6-5755b451e223';
</details>
Secrets can also have an optional _unique_ name and an optional description. These are also arguments to `vault.create_secret()`:
```sql
select vault.create_secret('another_s3kre3t', 'unique_name', 'This is the description');
```
<details>
<summary>Show Result</summary>
```sql
-[ RECORD 1 ]-----------------------------------------------------------------
id | 7095d222-efe5-4cd5-b5c6-5755b451e223
name | unique_name
@@ -115,12 +80,49 @@ created_at | 2022-12-14 02:34:23.85159+00
updated_at | 2022-12-14 02:34:23.85159+00
```
## Querying Data from the Vault
</details>
Alternatively, you can create a secret by `insert`ing data into the `vault.secret` table:
{/* prettier-ignore */}
```sql
insert into vault.secrets (secret)
values ('s3kre3t_k3y') returning *;
```
<details>
<summary>Show Result</summary>
```sql
-[ RECORD 1 ]-------------------------------------------------------------
id | d91596b8-1047-446c-b9c0-66d98af6d001
name |
description |
secret | S02eXS9BBY+kE3r621IS8beAytEEtj+dDHjs9/0AoMy7HTbog+ylxcS22A==
key_id | 7f5ad44b-6bd5-4c99-9f68-4b6c7486f927
nonce | \x3aa2e92f9808e496aa4163a59304b895
created_at | 2022-12-14 02:29:21.3625+00
updated_at | 2022-12-14 02:29:21.3625+00
```
</details>
### Viewing secrets
If you look in the `vault.secrets` table, you will see that your data is stored encrypted. To decrypt the data, there is an automatically created view `vault.decrypted_secrets`. This view will decrypt secret data on the fly:
{/* prettier-ignore */}
```sql
select *
from vault.decrypted_secrets
order by created_at desc
limit 3;
```
<details>
<summary>Show Result</summary>
```sql
postgres=> select * from vault.decrypted_secrets order by created_at desc limit 3;
-[ RECORD 1 ]----+-----------------------------------------------------------------
id | 7095d222-efe5-4cd5-b5c6-5755b451e223
name | unique_name
@@ -153,17 +155,30 @@ created_at | 2022-12-14 02:29:21.3625+00
updated_at | 2022-12-14 02:29:21.3625+00
```
</details>
Notice how this view has a `decrypted_secret` column that contains the decrypted secrets. Views are not stored on disk, they are only run at query time, so the secret remains encrypted on disk, and in any backup dumps or replication streams.
You should ensure that you protect access to this view with the appropriate SQL privilege settings at all times, as anyone that has access to the view has access to decrypted secrets.
## Updating Secrets
### Updating Secrets
A secret can be updated with the `vault.update_secret()` function, this function makes updating secrets easy, just provide the secret UUID as the first argument, and then an updated secret, updated optional unique name, or updated description:
```sql
postgres=> select vault.update_secret('7095d222-efe5-4cd5-b5c6-5755b451e223', 'n3w_upd@ted_s3kret',
'updated_unique_name', 'This is the updated description');
select
vault.update_secret(
'7095d222-efe5-4cd5-b5c6-5755b451e223',
'n3w_upd@ted_s3kret',
'updated_unique_name',
'This is the updated description'
);
```
<details>
<summary>Show Result</summary>
```sql
-[ RECORD 1 ]-+-
update_secret |
@@ -180,6 +195,38 @@ created_at | 2022-12-14 02:34:23.85159+00
updated_at | 2022-12-14 02:51:13.938396+00
```
</details>
## Deep Dive on How The Vault works
<div className="video-container">
<iframe
src="https://www.youtube-nocookie.com/embed/QHLPNDrdN2w"
title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen
></iframe>
</div>
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.
### Authenticated Encryption with Associated Data
The first important feature of TCE is that it uses an [Authenticated Encryption with Associated Data](<https://en.wikipedia.org/wiki/Authenticated_encryption#Authenticated_encryption_with_associated_data_(AEAD)>) encryption algorithm (based on libsodium).
### Encryption key location
**Authenticated Encryption** means that in addition to the data being encrypted, it is also signed so that it cannot be forged. You can guarantee that the data was encrypted by someone you trust, which you wouldn't get with encryption alone. The decryption function verifies that the signature is valid _before decrypting the value_.
**Associated Data** means that you can include any other columns from the same row as part of the signature computation. This doesn't encrypt those other columns - rather it ensures that your encrypted value is only associated with columns from that row. If an attacker were to copy an encrypted value from another row to the current one, the signature would be rejected (assuming you used a unique column in the associated data).
Another important feature of pgsodium is that the encryption keys are never stored in the database alongside the encrypted data. Instead, only a **Key ID** is stored, which is a reference to the key that is only accessible outside of SQL. Even if an attacker can capture a dump of your entire database, they will see only encrypted data and key IDs, _never the raw key itself_.
This is an important safety precaution - there is little value in storing the encryption key in the database itself as this would be like locking your front door but leaving the key in the lock! Storing the key outside the database fixes this issue.
Where are the keys stored? Supabase creates and manages the root keys (from which all key IDs are derived) in our secured backend systems. We keep this root key safe and separate from your data. You remain in control of your keys - a separate API endpoint is available that you can use to access the key if you want to decrypt your data outside of Supabase.
## Internal Details
To encrypt data, you need a _key id_. You can use the default key id created automatically for every project, or create your own key ids Using the `pgsodium.create_key()` function. Key ids are used to internally derive the encryption key used to encrypt secrets in the vault. Vault users typically do not have access to the key itself, only the key id.
@@ -209,7 +256,7 @@ And then restart your project from the dashboard to enable that change.
In the future we are researching various ways to refine the way statement logging interacts with sensitive columns.
## See also
## Resources
- Read more about Supabase Vault in the [blog post](https://supabase.com/blog/vault-now-in-beta)
- [Supabase Vault on GitHub](https://github.com/supabase/vault)
@@ -24,16 +24,25 @@ jobs:
env:
SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }}
PROJECT_ID: zdtdtxajzydjqzuktnqx
PROJECT_ID: your-project-id
steps:
- uses: actions/checkout@v3
- uses: supabase/setup-cli@v1
with:
version: 1.0.0
version: latest
- run: supabase functions deploy your-function-name --project-ref $PROJECT_ID
- run: supabase functions deploy --project-ref $PROJECT_ID
```
Since Supabase CLI [v1.62.0](https://github.com/supabase/cli/releases/tag/v1.62.0) you can deploy all functions with a single command.
Individual function configuration like [JWT verification](/docs/reference/cli/config#functions.function_name.verify_jwt) and [import map location](/docs/reference/cli/config#functions.function_name.import_map) can be set via the `config.toml` file.
```toml
[functions.hello-world]
verify_jwt = false
```
<div class="video-container">
@@ -40,9 +40,18 @@ jobs:
- uses: supabase/setup-cli@v1
with:
version: 1.0.0
version: latest
- run: supabase functions deploy github-action-deploy --project-ref $PROJECT_ID
- run: supabase functions deploy --project-ref $PROJECT_ID
```
Since Supabase CLI [v1.62.0](https://github.com/supabase/cli/releases/tag/v1.62.0) you can deploy all functions with a single command.
Individual function configuration like [JWT verification](/docs/reference/cli/config#functions.function_name.verify_jwt) and [import map location](/docs/reference/cli/config#functions.function_name.import_map) can be set via the `config.toml` file.
```toml
[functions.hello-world]
verify_jwt = false
```
export const Page = ({ children }) => <Layout meta={meta} children={children} />
@@ -41,6 +41,8 @@ This creates a function stub in your `supabase` folder at `./functions/hello-wor
## Deploy to production
### Deploy a specific function
```bash
supabase functions deploy hello-world
```
@@ -56,6 +58,21 @@ If you want to use Edge Functions to handle webhooks (e.g. [Stripe payment webho
</Admonition>
### Deploy all functions
```bash
supabase functions deploy
```
Since Supabase CLI [v1.62.0](https://github.com/supabase/cli/releases/tag/v1.62.0) you can deploy all functions with a single command. This is useful for example when [deploying with GitHub Actions](/docs/guides/functions/cicd-workflow).
Individual function configuration like [JWT verification](/docs/reference/cli/config#functions.function_name.verify_jwt) and [import map location](/docs/reference/cli/config#functions.function_name.import_map) can be set via the `config.toml` file.
```toml
[functions.hello-world]
verify_jwt = false
```
## Invoking remote functions
You can invoke Edge Functions using curl:
+3 -3
View File
@@ -102,9 +102,9 @@ export const meta = {
export const useCases = [
{
title: 'OpenAI Vector Search',
href: '/guides/getting-started/openai/vector-search',
description: `Build your own custom ChatGPT with Next.js, OpenAI and pg_vector.`,
title: 'AI, Vectors, and embeddings',
href: '/docs/guides/ai#examples',
description: `Build AI-enabled applications using our Vector toolkit.`,
icon: '/docs/img/icons/openai_logo',
},
{
@@ -19,7 +19,7 @@ The Dashboard provides a wide range of features for setting up your project: cre
2. **Easier Collaboration**: Developing locally can make it easier to collaborate with others on the same project.
3. **Cost-Effective**: Supabase provides a generous free tier and gives you two free projects to get started. But what if you need more than two? When you develop locally, you can spin up unlimited local projects and link them with live projects when you're ready to launch.
3. **Cost-Effective**: Supabase provides a generous free plan and gives you two free projects to get started. But what if you need more than two? When you develop locally, you can spin up unlimited local projects and link them with live projects when you're ready to launch.
4. **Configuration in code**: If you directly change your tables via the Dashboard, none of that gets captured in code. If you follow these local development practices, you'll store all of your table schemas in code.
@@ -13,7 +13,7 @@ In this guide, we will demonstrate how to create a new Supabase project, install
![Supabase App](/docs/img/guides/integrations/directus/supabase-20220608A.webp)
[Supabase](https://supabase.com/) is an open-source Firebase alternative that provides a PostgreSQL database, storage, authentication, and a dynamic REST API based on your schema. While it is possible to self-host Supabase on your own infrastructure, this article will focus on Supabase Cloud's Free tier, which is the fastest and easiest way to get started.
[Supabase](https://supabase.com/) is an open-source Firebase alternative that provides a PostgreSQL database, storage, authentication, and a dynamic REST API based on your schema. While it is possible to self-host Supabase on your own infrastructure, this article will focus on Supabase Cloud's Free plan, which is the fastest and easiest way to get started.
![Directus App](/docs/img/guides/integrations/directus/directus-20220608A.webp)
@@ -30,7 +30,7 @@ PolyScale provides caching for TCP connections and GraphQL. Support for caching
## Step 0: Create a PolyScale account
If you do not already have a PolyScale account, you can create an account [here](https://app.polyscale.ai/signup). PolyScale offers a free tier and no credit card is required.
If you do not already have a PolyScale account, you can create an account [here](https://app.polyscale.ai/signup). PolyScale offers a free plan and no credit card is required.
## Step 1: Create your PolyScale Cache
@@ -79,7 +79,7 @@ The table below shows the corresponding permissions for each available role you
prevents accidental invites to accounts not managed by your company's
enterprise systems.
[^2]: Available on the Teams and Enterprise Tiers.
[^2]: Available on the Teams and Enterprise Plans.
export const Page = ({ children }) => <Layout meta={meta} children={children} />
+3 -3
View File
@@ -21,7 +21,7 @@ Daily Backups and PITR are mutually exclusive. If your project opts into using P
## Daily Backups
All Pro and Enterprise tier Supabase projects are backed up automatically on a daily basis. In terms of Recovery Point Objective (RPO), Daily Backups would be suitable for projects willing to lose up to 24 hours worth of data if disaster hits at the most inopportune time. If a lower RPO is required, enabling Point-in-Time Recovery should be considered.
All Pro and Enterprise plan Supabase projects are backed up automatically on a daily basis. In terms of Recovery Point Objective (RPO), Daily Backups would be suitable for projects willing to lose up to 24 hours worth of data if disaster hits at the most inopportune time. If a lower RPO is required, enabling Point-in-Time Recovery should be considered.
<Admonition type="note">
For security purposes, passwords for custom roles are not stored in daily backups, and will not be
@@ -35,7 +35,7 @@ The PostgreSQL utility [pg_dumpall](https://www.postgresql.org/docs/current/app-
![Scheduled backups dashboard](/docs/img/backups-daily-dashboard.png)
You can access daily backups in the [Scheduled backups](https://app.supabase.com/project/_/database/backups/scheduled) settings in the Dashboard. Pro tier projects can access the last 7 days’ worth of daily backups while Enterprise tier projects can access up to 30 days’ worth of daily backups. Users can restore their project to any one of the backups or download them as a zipped SQL file.
You can access daily backups in the [Scheduled backups](https://app.supabase.com/project/_/database/backups/scheduled) settings in the Dashboard. Pro plan projects can access the last 7 days’ worth of daily backups while Enterprise plan projects can access up to 30 days’ worth of daily backups. Users can restore their project to any one of the backups or download them as a zipped SQL file.
### Restoration Process [#daily-backups-restoration-process]
@@ -52,7 +52,7 @@ The Dashboard will then prompt for a confirmation before proceeding with the res
Point-in-Time Recovery (PITR) allows a project to be backed up at much shorter intervals. This provides users an option to restore to any chosen point of up to seconds in granularity. Even with daily backups, a day’s worth of data could still be lost. With PITR, backups could be performed up to the point of disaster.
<Admonition type="note">
This feature is available to all Enterprise tier projects. Pro tier projects can enable PITR as an
This feature is available to all Enterprise plan projects. Pro plan projects can enable PITR as an
add-on.
</Admonition>
@@ -8,18 +8,20 @@ export const meta = {
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.
| Plan | Pricing | CPU | Memory | Maximum Disk IO Bandwidth | Baseline Disk IO Bandwidth | Connections: Direct (recommended) | Connections: Pooler (recommended) |
| --------------- | ------- | ----------------------- | ------ | ------------------------- | -------------------------- | --------------------------------- | --------------------------------- |
| Free (Included) | $0 | 2-core ARM (shared) | 1 GB | 2,606 Mbps | 87 Mbps | 60 | 200 |
| Small | $5 | 2-core ARM (shared) | 2 GB | 2,606 Mbps | 174 Mbps | 90 | 200 |
| Medium | $50 | 2-core ARM (shared) | 4 GB | 2,606 Mbps | 347 Mbps | 120 | 200 |
| Large | $100 | 2-core ARM (dedicated) | 8 GB | 4,750 Mbps | 630 Mbps | 160 | 300 |
| XL | $200 | 4-core ARM (dedicated) | 16 GB | 4,750 Mbps | 1,188 Mbps | 240 | 700 |
| 2XL | $400 | 8-core ARM (dedicated) | 32 GB | 4,750 Mbps | 2,375 Mbps | 380 | 1500 |
| 4XL | $950 | 16-core ARM (dedicated) | 64 GB | 4,750 Mbps | 4,750 Mbps | 480 | 3000 |
| 8XL | $1,860 | 32-core ARM (dedicated) | 128 GB | 9,500 Mbps | 9,500 Mbps | 490 | 6000 |
| 12XL | $2,790 | 48-core ARM (dedicated) | 192 GB | 14,250 Mbps | 14,250 Mbps | 500 | 9000 |
| 16XL | $3,720 | 64-core ARM (dedicated) | 256 GB | 19,000 Mbps | 19,000 Mbps | 500 | 12,000 |
| Plan | Pricing | CPU | Memory | Connections: Direct | Connections: Pooler |
| --------------- | ------- | ----------------------- | ------ | ------------------- | ------------------- |
| Free (Included) | $0 | 2-core ARM (shared) | 1 GB | 60 | 200 |
| Small | $5 | 2-core ARM (shared) | 2 GB | 90 | 200 |
| Medium | $50 | 2-core ARM (shared) | 4 GB | 120 | 200 |
| Large | $100 | 2-core ARM (dedicated) | 8 GB | 160 | 300 |
| XL | $200 | 4-core ARM (dedicated) | 16 GB | 240 | 700 |
| 2XL | $400 | 8-core ARM (dedicated) | 32 GB | 380 | 1500 |
| 4XL | $950 | 16-core ARM (dedicated) | 64 GB | 480 | 3000 |
| 8XL | $1,860 | 32-core ARM (dedicated) | 128 GB | 490 | 6000 |
| 12XL | $2,790 | 48-core ARM (dedicated) | 192 GB | 500 | 9000 |
| 16XL | $3,720 | 64-core ARM (dedicated) | 256 GB | 500 | 12,000 |
Number of connections above are recommended values.
[Contact us](https://supabase.com/contact/enterprise) if you require a custom plan.
@@ -31,11 +33,32 @@ All Postgres instances on Supabase are dedicated applications running inside ded
When considering compute upgrades, assess whether your bottlenecks are hardware-constrained or software-constrained. For example, you may want to look into [optimizing the number of connections](/docs/guides/platform/performance#optimizing-the-number-of-connections) or [examining query performance](/docs/guides/platform/performance#examining-query-performance). When you're happy with your Postgres instance's performance, then you can focus on additional compute resources. For example, you can load test your application in staging to understand your compute requirements. You can also start out on a smaller tier, [create a report](https://app.supabase.com/project/_/reports) in the Dashboard to monitor your CPU utilization, and upgrade later as needed
## Disk IO bandwidth
## Disk Throughput and IOPS
SSD Disks are attached to your servers and the disk performance of your workload is determined by the Disk IO bandwidth of this connection. Smaller compute instances can burst up to the maximum disk IO bandwidth for 30 minutes in a day. Beyond that, the performance reverts to the baseline disk IO bandwidth. For example, the free tier can burst up to 2,606 Mbps for 30 minutes a day and reverts to the baseline performance of 87 Mbps. If you need consistent disk performance, choose the 4XL or larger compute add-on which has the same baseline and maximum disk IO bandwidth.
SSD Disks are attached to your servers and the disk performance depends on the compute add-on of your instance.
If you're unsure of how many IOPS your application requires, you can load test your project and inspect these [metrics in the Dashboard](https://app.supabase.com/project/_/reports). If the `Daily Disk IO Budget % Remaining` stat is less than 100%, it indicates that your workload has burst beyond the baseline IO throughput during the day. If this metric drops to zero, the workload has used up all the burst IO throughput minutes during the day and is running at the baseline performance. These projects are good candidates for upgrading to a larger compute add with higher baseline throughput.
| Plan | Pricing | Max Disk Throughput | Baseline Disk Throughput | Max IOPS | Baseline IOPS |
| --------------- | ------- | ------------------- | ------------------------ | ----------- | ------------- |
| Free (Included) | $0 | 2,085 Mbps | 87 Mbps | 11,800 IOPS | 500 IOPS |
| Small | $5 | 2,085 Mbps | 174 Mbps | 11,800 IOPS | 1,000 IOPS |
| Medium | $50 | 2,085 Mbps | 347 Mbps | 11,800 IOPS | 2,000 IOPS |
| Large | $100 | 4,750 Mbps | 630 Mbps | 20,000 IOPS | 3,600 IOPS |
| XL | $200 | 4,750 Mbps | 1,188 Mbps | 20,000 IOPS | 6,000 IOPS |
| 2XL | $400 | 4,750 Mbps | 2,375 Mbps | 20,000 IOPS | 12,000 IOPS |
| 4XL | $950 | 4,750 Mbps | 4,750 Mbps | 20,000 IOPS | 20,000 IOPS |
| 8XL | $1,860 | 9,500 Mbps | 9,500 Mbps | 40,000 IOPS | 40,000 IOPS |
| 12XL | $2,790 | 14,250 Mbps | 14,250 Mbps | 50,000 IOPS | 50,000 IOPS |
| 16XL | $3,720 | 19,000 Mbps | 19,000 Mbps | 80,000 IOPS | 80,000 IOPS |
[Contact us](https://supabase.com/contact/enterprise) if you require a custom plan.
### Bursting and Daily Disk Budget
Smaller compute instances can burst up to their largest throughput and IOPS for 30 minutes in a day. Beyond that, the performance reverts to the baseline. For example, the free tier can burst up to 2,085 Mbps for 30 minutes a day and reverts to the baseline performance of 87 Mbps. Your disk budget gets replenished throughout the day.
If you need consistent disk performance, choose the 4XL or larger compute add-on which has the same baseline and maximum disk throughput and IOPS.
If you're unsure of how much throughput or IOPS your application requires, you can load test your project and inspect these [metrics in the Dashboard](https://app.supabase.com/project/_/reports). If the `Daily Disk IO Budget % Remaining` stat is less than 100%, it indicates that your workload has burst beyond the baseline IO throughput during the day. If this metric drops to zero, the workload has used up all the burst IO throughput minutes during the day and is running at the baseline performance. These projects are good candidates for upgrading to a larger compute add-on with higher baseline throughput.
export const Page = ({ children }) => <Layout meta={meta} children={children} />
@@ -7,7 +7,7 @@ export const meta = {
video: 'https://www.youtube.com/v/6rcGnW_Mh-0',
}
Custom domains allow you to present a branded experience to your users. Custom domains are available as a [add-on for projects on a paid tier](https://app.supabase.com/project/_/settings/billing/update). Setting up a custom domain requires [Owner or Admin permissions](/docs/guides/platform/access-control#manage-team-members) for the project. Currently, you must use a subdomain (e.g., `api.example.com`, rather than `example.com`) for the purposes of this guide.
Custom domains allow you to present a branded experience to your users. Custom domains are available as a [add-on for projects on a paid plan](https://app.supabase.com/project/_/settings/billing/update). Setting up a custom domain requires [Owner or Admin permissions](/docs/guides/platform/access-control#manage-team-members) for the project. Currently, you must use a subdomain (e.g., `api.example.com`, rather than `example.com`) for the purposes of this guide.
<div className="video-container">
<iframe
@@ -100,7 +100,7 @@ To get started:
1. [Install](/docs/guides/resources/supabase-cli) the Supabase CLI 1.22.0+.
1. [Log in](/docs/guides/resources/supabase-getting-started/local-development#log-in-to-the-supabase-cli) to your Supabase account using the CLI.
1. Ensure that you have [Owner or Admin permissions](/docs/guides/platform/access-control#manage-team-members) for the project you'd like to set up a vanity subdomain for.
1. Ensure that your project has a paid subscription (Pro/Pay as you go/Enterprise tier) in the [Billing page of the Dashboard](https://app.supabase.com/project/_/settings/billing/subscription).
1. Ensure that your project has a paid subscription (Pro/Pay as you go/Enterprise plan) in the [Billing page of the Dashboard](https://app.supabase.com/project/_/settings/billing/subscription).
## Configure a Vanity Subdomain
@@ -29,7 +29,7 @@ Database Space is consumed primarily by your data, indexes, and materialized vie
<Admonition type="note">
Depending on your billing tier, your database can go into read-only mode which can prevent you inserting and deleting data. There are instructions for managing read-only mode in the [Disk Management](#disk-management) section.
Depending on your billing plan, your database can go into read-only mode which can prevent you inserting and deleting data. There are instructions for managing read-only mode in the [Disk Management](#disk-management) section.
</Admonition>
@@ -55,15 +55,15 @@ New Supabase projects have a database size of ~40-60mb. This space includes pre-
## Disk management
Supabase uses network-attached storage to balance performance with scalability. The behavior of your disk depends on your billing tier.
Supabase uses network-attached storage to balance performance with scalability. The behavior of your disk depends on your billing plan.
### Paid Tier Behavior
### Paid Plan Behavior
Pro and Enterprise projects have auto-scaling Disk Storage.
Disk Storage expands automatically when the database reaches 90% of the disk size. The disk is expanded to be 50% larger (e.g., 8GB -> 12GB). Auto-scaling can only take place once every 6 hours. If within those 6 hours you reach 95% of the disk space, your project will enter read-only mode.
The maximum Disk Storage Size for Pro Tier is 64TB. If you need more than this, [contact us](https://app.supabase.com/support/new) to learn more about the Enterprise plan.
The maximum Disk Storage Size for Pro plan is 64TB. If you need more than this, [contact us](https://app.supabase.com/support/new) to learn more about the Enterprise plan.
The Disk Storage Size can also be manually expanded in [Database settings page](https://app.supabase.com/project/_/settings/database). The maximum default you can expand the Disk Storage to is 200GB. If you wish to manually expand it any more than 200B, please [contact us](https://app.supabase.com/support/new) to discuss expanding the 200GB limit.
@@ -73,11 +73,11 @@ You may want to import a lot of data into your database which requires multiple
</Admonition>
### Free Tier Behavior
### Free Plan Behavior
Free Tier projects enter [read-only](#read-only-mode) mode when you exceed the 500mb limit. Once in read-only mode, you have several options:
Free Plan projects enter [read-only](#read-only-mode) mode when you exceed the 500mb limit. Once in read-only mode, you have several options:
- [Upgrade to the Pro or Enterprise tier](https://app.supabase.com/project/_/settings/billing/subscription) to enable auto-scaling and expand beyond the 500mb database size limit.
- [Upgrade to the Pro or Enterprise plan](https://app.supabase.com/project/_/settings/billing/subscription) to enable auto-scaling and expand beyond the 500mb database size limit.
- [Disable read-only mode](#disabling-read-only-mode) and reduce your database size.
### Read-only mode
@@ -42,14 +42,14 @@ After developing your project and deciding it's Production Ready, you should run
- Use your own SMTP credentials so that you have full control over the deliverability of your transactional auth emails (see Auth > Settings)
- you can grab SMTP credentials from any major email provider such as SendGrid, AWS SES, etc.
- The default rate limit for auth emails provided by Supabase is 30 new users per hour, if doing a major public announcement you will likely require more than this.
- If your application is on the free tier and is **not** expected to be queried at least once every 7 days, then it may be paused by Supabase to save on server resources.
- If your application is on the free plan and is **not** expected to be queried at least once every 7 days, then it may be paused by Supabase to save on server resources.
- You can restore paused projects from the Supabase dashboard.
- Upgrade to Pro to guarantee that your project will not be paused for inactivity.
- Database backups are not available for download on the free tier.
- Database backups are not available for download on the free plan.
- You can set up your own backup systems using tools like [pg_dump](https://www.postgresqltutorial.com/postgresql-backup-database/) or [wal-g](https://github.com/wal-g/wal-g).
- Nightly backups for Pro tier projects are available on the Supabase dashboard for up to 7 days.
- Nightly backups for Pro plan projects are available on the Supabase dashboard for up to 7 days.
- Point in Time Recovery (PITR) allows a project to be backed up at much shorter intervals. This provides users an option to restore to any chosen point of up to seconds in granularity. In terms of Recovery Point Objective (RPO), Daily Backups would be suitable for projects willing to lose up to 24 hours worth of data. If a lower RPO is required, enable PITR.
- Upgrading to the Supabase Pro Tier will give you [access to our support team](https://app.supabase.com/support/new).
- Upgrading to the Supabase Pro plan will give you [access to our support team](https://app.supabase.com/support/new).
## Rate Limiting, Resource Allocation, & Abuse Prevention
@@ -34,7 +34,7 @@ The Supabase platform offers several HTTP APIs for each project. These APIs can
The project the request was being made against has been paused. The project cannot process requests until it is un-paused by the owner.
Free-tier projects may be paused due to inactivity, on request by the owner, or in rare instances, due to abuse.
Free-plan projects may be paused due to inactivity, on request by the owner, or in rare instances, due to abuse.
export const Page = ({ children }) => <Layout meta={meta} children={children} />
@@ -6,7 +6,7 @@ export const meta = {
description: 'Getting the best results out of your Supabase project',
}
The Supabase platform automatically optimizes your Postgres database to take advantage of the compute resources of the tier your project is on. However, these optimizations are based on assumptions about the type of workflow the project is being utilized for, and it is likely that better results can be obtained by tuning the database for your particular workflow.
The Supabase platform automatically optimizes your Postgres database to take advantage of the compute resources of the plan your project is on. However, these optimizations are based on assumptions about the type of workflow the project is being utilized for, and it is likely that better results can be obtained by tuning the database for your particular workflow.
## Examining Query Performance
@@ -124,7 +124,7 @@ Queries which are the most time consuming are not necessarily bad, you may have
Generally for most applications a small percentage of data is accessed more regularly than the rest. To make sure that your regularly accessed data is available, Postgres tracks your data access patterns and keeps this in its [shared_buffers](https://www.postgresql.org/docs/15/runtime-config-resource.html#RUNTIME-CONFIG-RESOURCE-MEMORY) cache.
Applications with lower cache hit rates generally perform more poorly since they have to hit the disk to get results rather than serving them from memory. Very poor hit rates can also cause you to burst past your [Disk I/O limits](https://supabase.com/docs/guides/platform/compute-add-ons#disk-io-bandwidth) causing significant performance issues.
Applications with lower cache hit rates generally perform more poorly since they have to hit the disk to get results rather than serving them from memory. Very poor hit rates can also cause you to burst past your [Disk I/O limits](https://supabase.com/docs/guides/platform/compute-add-ons#disk-throughput-and-iops) causing significant performance issues.
You can view your cache and index hit rate by executing the following query:
@@ -31,7 +31,7 @@ Some common solutions for this issue are:
- [Using fewer Postgres connections](../platform/performance#configuring-clients-to-use-fewer-connections) can reduce the amount of resources needed on the project.
- [Restarting](https://app.supabase.com/project/_/settings/general) the database. This only temporarily solves the issue by terminating any ongoing workloads that might be tying up your compute resources.
If your [Daily Disk IO budget](../platform/compute-add-ons#disk-io-bandwidth) has been drained, you will need to either wait for it to be replenished the next day, or upgrade to a larger compute add-on to increase the budget available to your project.
If your [Daily Disk IO budget](../platform/compute-add-ons#disk-throughput-and-iops) has been drained, you will need to either wait for it to be replenished the next day, or upgrade to a larger compute add-on to increase the budget available to your project.
## Unable to connect to your Supabase Project
+1 -2
View File
@@ -45,8 +45,7 @@ The cache status of a particular request is sent in the `cf-cache-status` header
## Smart CDN Caching
<Admonition type="note">
Smart CDN caching is automatically enabled for [Pro tiers and
above](https://supabase.com/pricing).
Smart CDN caching is automatically enabled for [Pro plan and above](https://supabase.com/pricing).
</Admonition>
With Smart CDN caching enabled, the asset metadata in your database is synchronized to the edge. This automatically revalidates the cache when the asset is changed or deleted.
@@ -11,7 +11,7 @@ export const meta = {
Supabase Storage offers the functionality to transform and resize images dynamically. Any image stored in your buckets can be transformed and optimized for fast delivery.
<Admonition type="note">
Image Resizing is currently in beta and enabled for [Pro tier and
Image Resizing is currently in beta and enabled for [Pro plan and
above](https://supabase.com/pricing).
</Admonition>
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="117" height="20"><linearGradient id="b" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="a"><rect width="117" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#a)"><path fill="#555" d="M0 0h30v20H0z"/><path fill="#007ec6" d="M30 0h87v20H30z"/><path fill="url(#b)" d="M0 0h117v20H0z"/></g><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="110"><svg x="4px" y="0px" width="22px" height="20px" viewBox="-2 0 28 24" style="background-color: #fff;border-radius: 1px;"><path style="fill:#e8710a;" d="M1.977,16.77c-2.667-2.277-2.605-7.079,0-9.357C2.919,8.057,3.522,9.075,4.49,9.691c-1.152,1.6-1.146,3.201-0.004,4.803C3.522,15.111,2.918,16.126,1.977,16.77z"/><path style="fill:#f9ab00;" d="M12.257,17.114c-1.767-1.633-2.485-3.658-2.118-6.02c0.451-2.91,2.139-4.893,4.946-5.678c2.565-0.718,4.964-0.217,6.878,1.819c-0.884,0.743-1.707,1.547-2.434,2.446C18.488,8.827,17.319,8.435,16,8.856c-2.404,0.767-3.046,3.241-1.494,5.644c-0.241,0.275-0.493,0.541-0.721,0.826C13.295,15.939,12.511,16.3,12.257,17.114z"/><path style="fill:#e8710a;" d="M19.529,9.682c0.727-0.899,1.55-1.703,2.434-2.446c2.703,2.783,2.701,7.031-0.005,9.764c-2.648,2.674-6.936,2.725-9.701,0.115c0.254-0.814,1.038-1.175,1.528-1.788c0.228-0.285,0.48-0.552,0.721-0.826c1.053,0.916,2.254,1.268,3.6,0.83C20.502,14.551,21.151,11.927,19.529,9.682z"/><path style="fill:#f9ab00;" d="M4.49,9.691C3.522,9.075,2.919,8.057,1.977,7.413c2.209-2.398,5.721-2.942,8.476-1.355c0.555,0.32,0.719,0.606,0.285,1.128c-0.157,0.188-0.258,0.422-0.391,0.631c-0.299,0.47-0.509,1.067-0.929,1.371C8.933,9.539,8.523,8.847,8.021,8.746C6.673,8.475,5.509,8.787,4.49,9.691z"/><path style="fill:#f9ab00;" d="M1.977,16.77c0.941-0.644,1.545-1.659,2.509-2.277c1.373,1.152,2.85,1.433,4.45,0.499c0.332-0.194,0.503-0.088,0.673,0.19c0.386,0.635,0.753,1.285,1.181,1.89c0.34,0.48,0.222,0.715-0.253,1.006C7.84,19.73,4.205,19.188,1.977,16.77z"/></svg><text x="245" y="140" transform="scale(.1)" textLength="30"> </text><text x="725" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="770">Open in Colab</text><text x="725" y="140" transform="scale(.1)" textLength="770">Open in Colab</text></g> </svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

@@ -58,13 +58,13 @@ The two services price quite differently, the key differences being:
- [Supabase pricing is based around usage](https://supabase.com/pricing), so you only pay for what you use.
- Heroku prices based on a tier model with [37 plans to choose from](https://elements.heroku.com/addons/heroku-postgresql#pricing).
Supabase’s free tier also includes a dedicated Postgres instance, and the best bit is you can upgrade to pro later without any interruptions.
Supabase’s free plan also includes a dedicated Postgres instance, and the best bit is you can upgrade to pro later without any interruptions.
### Global Deployments
You may have strict data regulations that you must comply with, so choosing your region can be very important. Here’s how the deployment options stack up:
- Supabase can be deployed to any one of [12 data centers across the globe](https://github.com/supabase/supabase/discussions/4815#discussioncomment-1915129) (free tier included).
- Supabase can be deployed to any one of [12 data centers across the globe](https://github.com/supabase/supabase/discussions/4815#discussioncomment-1915129) (free plan included).
- Since Supabase is fully open source - you can also [self host wherever you like](https://supabase.com/docs/guides/hosting/overview).
- You can deploy Heroku Postgres to two data centers (US and Europe) however [6 more data centers](https://devcenter.heroku.com/articles/regions) are available on the Enterprise plan.
@@ -133,4 +133,4 @@ We look forward to showing off some more Supabase + Replit examples.
You can find my example repl here: [https://repl.it/@awalias/supabase-test#index.js](https://repl.it/@awalias/supabase-test#index.js)
Supabase has a free tier, head over to [https://app.supabase.com](https://app.supabase.com) to get started.
Supabase has a free plan, head over to [https://app.supabase.com](https://app.supabase.com) to get started.
+3 -3
View File
@@ -47,15 +47,15 @@ We're announcing pricing today. If you don’t care for the details, [here is th
The [blog post here](/blog/pricing) goes into depth around our decision-making process.
The key takeaway is that we will stick with (what we feel is) a generous free tier. This was critical for many of our users who are still in the “build” phase of their project/business.
The key takeaway is that we will stick with (what we feel is) a generous free plan. This was critical for many of our users who are still in the “build” phase of their project/business.
Another key takeaway is predictability. We’ve talked to too many devs who have been caught out by Firebase’s usage billing.
Usually this is due to rogue API requests, which have a habit of spiking without any good debugging tools. To circumvent this, our usage pricing is centered on more “predictable” mechanisms (like storage), avoiding usage-billing on items like API requests.
Even on our free tier you’re able to make millions of requests per day (we have developers doing this already!). In the future we will introduce “usage caps” on our top tier, which will allow you to specify maximum spends. Finally, we’re starting with soft-tiers, so nobody will be “unexpectedly” upgraded.
Even on our free plan you’re able to make millions of requests per day (we have developers doing this already!). In the future we will introduce “usage caps” on our top tier, which will allow you to specify maximum spends. Finally, we’re starting with soft-tiers, so nobody will be “unexpectedly” upgraded.
We’re a startup ourselves, so we want to build a sustainable business. Even though we are offering a free tier, we’re confident in our ability to support it long-term with the enterprise features we are building (SSO, multi-cloud).
We’re a startup ourselves, so we want to build a sustainable business. Even though we are offering a free plan, we’re confident in our ability to support it long-term with the enterprise features we are building (SSO, multi-cloud).
If you aren’t confident in our business model though, we’re open source. You can [host it yourself](https://github.com/supabase/supabase/tree/master/docker). Even better, everything we build is centered around Postgres, so you can pretty much `pgdump` your database and take it to your favorite Postgres platform.
+10 -10
View File
@@ -11,7 +11,7 @@ date: '03-29-2021'
## Pricing is hard
Many developers have been waiting for [pricing](/pricing) before building on Supabase. It has taken this long to announce pricing for one simple reason: pricing is hard. We've spent countless hours discussing and testing different models. We consulted users, OSS veterans, and SaaS Gurus. We explored and ruled out a number of options including: no free tier, pure usage based, pay-per-seat, pay-per-row, and "bolt-on" feature based pricing.
Many developers have been waiting for [pricing](/pricing) before building on Supabase. It has taken this long to announce pricing for one simple reason: pricing is hard. We've spent countless hours discussing and testing different models. We consulted users, OSS veterans, and SaaS Gurus. We explored and ruled out a number of options including: no free plan, pure usage based, pay-per-seat, pay-per-row, and "bolt-on" feature based pricing.
Supabase is a suite of integrated products - should we compare our Postgres databases with Amazon RDS? Do we benchmark our Auth pricing against Auth0 or Okta?
@@ -19,11 +19,11 @@ So we decided to do what we always do: ship it, share it, and then listen to our
## TLDR
We are launching [pricing](/pricing) with three tiers:
We are launching [pricing](/pricing) with three plans:
1. Free - for up to four hobby-level projects
2. Pro - $25 per project per month, for more serious projects
3. Usage based - for those who need more resource than what's on the Pro tier
3. Usage based - for those who need more resource than what's on the Pro plan
## Goals for our pricing structure
@@ -33,7 +33,7 @@ Our goals for pricing are simple:
- Price based on predictable metrics (no shock bills at the end of the month)
- Enable and grow with our most successful users, providing a pricing model that supports their growth
We came up with a 3-tier model to offer predictability, and then iterated through numerous user interviews to answer the questions: "how much do we charge in each tier, and what features go in each tier?". To answer these questions we considered:
We came up with a 3-plan model to offer predictability, and then iterated through numerous user interviews to answer the questions: "how much do we charge in each plan, and what features go in each plan?". To answer these questions we considered:
1. How much value does Supabase create compared to alternatives including Firebase, authentication providers like Okta and Auth0, API, and storage providers?
2. How much time does Supabase save developers?
@@ -41,13 +41,13 @@ We came up with a 3-tier model to offer predictability, and then iterated throug
4. As developers ourselves, how do we feel about our proposed pricing?
5. The underlying cost of development and maintenance of Supabase.
As a result, we've arrived at a pricing model that brings predictability, is sustainable, and enables us to continue to offer a free tier.
As a result, we've arrived at a pricing model that brings predictability, is sustainable, and enables us to continue to offer a free plan.
Our model allows anybody to come in and experience the full power of the Supabase stack without first needing to put down a credit card.
## Launching with a free tier
## Launching with a free plan
Many of our early users are building or migrating their passion projects to Supabase. Offering a free tier enables this. We also support a large number of students learning SQL for the first time, and fledgling startups which haven't started earning any revenue yet.
Many of our early users are building or migrating their passion projects to Supabase. Offering a free plan enables this. We also support a large number of students learning SQL for the first time, and fledgling startups which haven't started earning any revenue yet.
Developers need time to build their side projects, and they don't want to pay for a database while they do it. At the same time, hosting Postgres databases is expensive. So we decided to take the same strategy that we always have: ship, share it, and iterate.
@@ -61,15 +61,15 @@ We will issue credits for those who signed up during alpha, beta, and those who
We know that Firebase pricing can be [problematic](https://medium.com/madhash/how-not-to-get-a-30k-bill-from-firebase-37a6cb3abaca).
That's why we made our pricing as predictable as possible. You can quickly understand which pricing tier you fit into, without having to forecast how many API requests you're going to make this quarter.
That's why we made our pricing as predictable as possible. You can quickly understand which pricing plan you fit into, without having to forecast how many API requests you're going to make this quarter.
What does this mean for you as a developer?
- You can make as many API calls as you need to (within the constraints of your database). We already have users making millions of requests per day.
- We have been deliberately generous with authentication and storage on the free tier so that you get as much of the Supabase experience as possible.
- We have been deliberately generous with authentication and storage on the free plan so that you get as much of the Supabase experience as possible.
- We're starting with soft-limits for usage billing to prevent unexpected charges.
- We'll reach out to you before changing your billing plan to see if it makes sense for your project.
- On our "Pay as you go" tier we will work with you to forecast billing before making a commitment.
- On our "Pay as you go" plan we will work with you to forecast billing before making a commitment.
## Next steps
@@ -55,7 +55,7 @@ Now you can run Supabase locally in the terminal with `supabase start`. We hav
## Pricing
Our most frequently asked question by far is "ok supabase is sweet, but how much is it going to cost?". TL;DR there's **Free Tier** up to 500mb + 10k auth users, a **Pro** **Tier** at $25/month for 8GB + 100k auth users, and anything additional is charged on a usage basis.
Our most frequently asked question by far is "ok supabase is sweet, but how much is it going to cost?". TL;DR there's **Free Plan** up to 500mb + 10k auth users, a **Pro** **Plan** at $25/month for 8GB + 100k auth users, and anything additional is charged on a usage basis.
See [Pricing Page](/pricing) for full details and also our blog on why [pricing is hard](/blog/pricing).
@@ -86,4 +86,4 @@ Migrating to Supabase helped the team at Mobbin instantly improve the end-user e
They now add new features with confidence, and they continue to help creators all over the globe find inspiration for their next project.
You can [check out Mobbin on their website](https://mobbin.design/browse/ios/apps).
Sign up to Supabase's free tier and set up a scalable backend in less than 2 minutes.
Sign up to Supabase's free plan and set up a scalable backend in less than 2 minutes.
@@ -74,4 +74,4 @@ Replenysh clients include brands with global recognition and millions of custome
## Supabase help Replenysh continue to scale
Supabase turbo-charged Replenysh's development time with a seamless end-user mobile login experience. They are ready to help major brands engage with their users through participation in the circular economy. With Supabase, the team knows they have a slick auth experience, reduced DevOps overhead, and can continue to scale with Postgres. You can test out the Supabase developer experience today by [starting a new project on the free tier!.](https://app.supabase.com/)
Supabase turbo-charged Replenysh's development time with a seamless end-user mobile login experience. They are ready to help major brands engage with their users through participation in the circular economy. With Supabase, the team knows they have a slick auth experience, reduced DevOps overhead, and can continue to scale with Postgres. You can test out the Supabase developer experience today by [starting a new project on the free plan!.](https://app.supabase.com/)
@@ -138,7 +138,7 @@ Another nice side effect of making PostgreSQL do the heavy lifting is that Graph
## Performance
Each [free tier database](https://supabase.com/pricing) on the Supabase platform runs on a dedicated AWS t4g.micro instance with 2 vCPUs and 1 GB of memory.
Each [free plan database](https://supabase.com/pricing) on the Supabase platform runs on a dedicated AWS t4g.micro instance with 2 vCPUs and 1 GB of memory.
To squeeze the most out of that limited hardware we had to make a few significant optimizations:
@@ -216,7 +216,7 @@ Batching similar operations reduces network round-trips and time spent in the da
**Result**
As a result of these optimizations, the throughput of a “hello world” equivalent query on Supabase free-tier hardware is:
As a result of these optimizations, the throughput of a “hello world” equivalent query on Supabase free-plan hardware is:
- 377.4 requests/second through the API (mean)
- 656.2 queries/second through SQL (single connection, mean)
@@ -119,7 +119,7 @@ As our platform continues its rapid adoption within the developer community, we'
## Enterprise features for everybody
Some features are too good to limit to large customers, so today we're introducing a few enterprise features into the Pro tier.
Some features are too good to limit to large customers, so today we're introducing a few enterprise features into the Pro plan.
### Spend caps
@@ -130,10 +130,10 @@ Some features are too good to limit to large customers, so today we're introduci
/>
</video>
To simplify pricing, we've merged the “Pro” and “Pay as you go” tiers and introduced monthly spend caps to avoid nasty billing surprises.
To simplify pricing, we've merged the “Pro” and “Pay as you go” plans and introduced monthly spend caps to avoid nasty billing surprises.
These changes are to keep Supabase pricing [predictable, transparent, and developer friendly](/blog/pricing).
When you upgrade to the Pro Tier, spend caps are turned on by default, limiting your per-project costs to $25 per month.
When you upgrade to the Pro Plan, spend caps are turned on by default, limiting your per-project costs to $25 per month.
We're also retaining our soft limits while we manage the transition to granular spend-caps, so your service will continue to run even if your usage exceeds $25 (we'll contact you directly when you go over the limit). Right now there is a global project spend-cap, and in the future you'll have full control with configurable spend-caps on a “per-feature” basis.
### Database Add-ons
@@ -167,9 +167,9 @@ And if you're new to SQL, we have plenty of templates included.
This is just one of the exciting features we are releasing through our [Logflare acquisition](/blog/supabase-acquires-logflare).
Log history is available to every Supabase project [^1]:
- Free Tier: 1 day of log history
- Pro Tier: 7 days of log history
- Enterprise Tier: 90 days of log history
- Free Plan: 1 day of log history
- Pro Plan: 7 days of log history
- Enterprise Plan: 90 days of log history
### Elixir Livebooks
@@ -205,7 +205,7 @@ If you want to monitor your own PgBouncer connections you can easily [spin up a
## Enterprise Features
With the release of our new Enterprise Tier, we're announcing a tonne of new features for Enterprise customers.
With the release of our new Enterprise Plan, we're announcing a tonne of new features for Enterprise customers.
### Point-in-Time Recovery
@@ -227,7 +227,7 @@ If you want access to the Prometheus Endpoint, [contact the Supabase Enterprise
### SLAs & Enterprise Support
For our enterprise customers we know that service level agreements and support response times are also critical features. We continue to treat support as an
important priority for all tiers, though enterprise users require confidence that our response times meet their business needs. We now offer faster response times for Enterprise customers,
important priority for all plans, though enterprise users require confidence that our response times meet their business needs. We now offer faster response times for Enterprise customers,
alongside Priority and Priority Plus support packages for those who need more comprehensive support. You can find further details around our SLAs & support
[in our documentation](/docs/company/sla).
@@ -235,7 +235,7 @@ alongside Priority and Priority Plus support packages for those who need more co
![enterprise-pricing](/images/blog/launch-week-4/enterprise-day/enterprise-pricing.png)
If you need more information on our Enterprise tier, Pricing, SLAs, Support packages, or want to learn more about how Supabase can meet your scaling needs just
If you need more information on our Enterprise plan, Pricing, SLAs, Support packages, or want to learn more about how Supabase can meet your scaling needs just
[contact us](/contact/enterprise).
## Coming soon
@@ -131,7 +131,7 @@ Data visualization empowers organizations to turn unused data into actionable in
![screen shot of a meme saying visualization works every time](/images/blog/python-1/final-meme.png)
With our [free tier Supabase account](https://app.supabase.com/), you can start a new project today and use Metabase to visualize your app data.
With our [free plan Supabase account](https://app.supabase.com/), you can start a new project today and use Metabase to visualize your app data.
If you have any questions please reach out via [Twitter](https://twitter.com/supabase) or join our [Discord](https://discord.supabase.com).
@@ -76,7 +76,7 @@ You will be prompted to sign in using your Github account with a big green butto
![Create new Supabase project](/images/blog/flutter-chat/create-new-supabase-project.png)
You will be entering a few things here like the name of the project. You can call it “chat” for now. For the database password, go ahead and hit the “Generate a password” button to generate a random password. We won't use this password in this app, but if you ever need it, you can always override it later to whatever you want it to be. You can leave the pricing plan for free as Supabase has a very generous free tier that will be way more than enough for our chat app. Once you have entered everything, you can press the “Create new Project” button. Spinning up a brand new Supabase project could take a few minutes.
You will be entering a few things here like the name of the project. You can call it “chat” for now. For the database password, go ahead and hit the “Generate a password” button to generate a random password. We won't use this password in this app, but if you ever need it, you can always override it later to whatever you want it to be. You can leave the pricing plan for free as Supabase has a very generous free plan that will be way more than enough for our chat app. Once you have entered everything, you can press the “Create new Project” button. Spinning up a brand new Supabase project could take a few minutes.
Once your project is ready, we can dive into setting up our project!
@@ -414,9 +414,9 @@ if __name__ == '__main__':
main()
```
Of course, we aren't doing proper Python example if we don't make a test notebook available:
Of course, we aren't doing a proper Python example if we don't make a test notebook available:
[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/mansueli/SlackConsolidate/blob/main/SlackConsolidate_bot.ipynb)
[Open in Colab](https://colab.research.google.com/github/mansueli/SlackConsolidate/blob/main/SlackConsolidate_bot.ipynb)
## Conclusion
@@ -22,7 +22,7 @@ Today, we're excited to announce the general availability of Realtime's multipla
Here are the key takeaways:
- We've added [Broadcast](https://supabase.com/docs/guides/realtime/broadcast) and [Presence](https://supabase.com/docs/guides/realtime/presence) to our Realtime server. You can use these features with the new [supabase-js](https://supabase.com/docs/reference/javascript/next) release.
- All active Supabase projects on the free tier have access to the these features.
- All active Supabase projects on the free plan have access to the these features.
- All new Supabase projects created from August 18th have access to these features.
- We will work with all other projects to migrate to the new Realtime over the next few weeks. If you want immediate access, [reach out](https://app.supabase.com/support/new).
- [Chris McCord](https://twitter.com/chris_mccord), the creator of Phoenix Framework, is now a technical advisor.
@@ -64,7 +64,7 @@ We're making some changes to the way the Dashboard interacts with your database.
![Migrate from Heroku to Supabase](/images/blog/2022-september/migrate-from-heroku-to-supabase.jpg)
In case you missed it, Heroku is shutting down their free tier. We're big fans of Heroku and take a lot of inspiration from what they've built. Supabase is compatible with Heroku's PostgreSQL product (because we're just Postgres too), and if you have a free project running on Heroku we've created a [tool to help migrate](https://migrate.supabase.com/) to our free tier.
In case you missed it, Heroku is shutting down their free plan. We're big fans of Heroku and take a lot of inspiration from what they've built. Supabase is compatible with Heroku's PostgreSQL product (because we're just Postgres too), and if you have a free project running on Heroku we've created a [tool to help migrate](https://migrate.supabase.com/) to our free plan.
## Made with Supabase
@@ -85,13 +85,13 @@ Behind the scenes, we've provided a few small niceties:
<div className="bg-gray-300 rounded-lg p-6 italic">
Image Resizing is in Beta. While in Beta, it is only enabled for Pro-tier and Enterprise customers.
Image Resizing is in Beta. While in Beta, it is only enabled for Pro-plan and Enterprise customers.
</div>
## Smart CDN
Resizing images can get expensive, so before this release we knew we needed a better caching system. We already provide a “basic” CDN for all projects (including Free-tier), so the important update here is some smarter cache invalidation whenever there are changes.
Resizing images can get expensive, so before this release we knew we needed a better caching system. We already provide a “basic” CDN for all projects (including Free-plan), so the important update here is some smarter cache invalidation whenever there are changes.
![Caching is hard](/images/blog/launch-week-6/day-2-storage-resize/caching.png)
@@ -179,7 +179,7 @@ The green line here shows the number of cache hits to our CDN. As you can see, w
## Availability and Pricing
Image Resizing is in Beta. While in Beta, it is only enabled for Pro-tier and Enterprise customers.
Image Resizing is in Beta. While in Beta, it is only enabled for Pro-plan and Enterprise customers.
When choosing a pricing model there were a few options that competitors offered:
+4 -4
View File
@@ -19,9 +19,9 @@ Since our first platform release, v0.2.1, the feature set of pg_graphql has stea
## Background
pg_graphql was created to satisfy an extreme set of constraints. Mainly, [Supabase free tier](https://supabase.com/pricing) projects run on servers with 1 GB of memory. On those servers, we squeeze tuned versions of [PostgreSQL](https://github.com/supabase/postgres), [PostgREST](https://postgrest.org/en/stable/), and [GoTrue](https://github.com/supabase/gotrue). Every megabyte consumed by something that isn't PostgreSQL is another chance for an index to fall out of memory, or a large query to fail.
pg_graphql was created to satisfy an extreme set of constraints. Mainly, [Supabase free plan](https://supabase.com/pricing) projects run on servers with 1 GB of memory. On those servers, we squeeze tuned versions of [PostgreSQL](https://github.com/supabase/postgres), [PostgREST](https://postgrest.org/en/stable/), and [GoTrue](https://github.com/supabase/gotrue). Every megabyte consumed by something that isn't PostgreSQL is another chance for an index to fall out of memory, or a large query to fail.
Our philosophy when adding to the stack is to use existing open source tools wherever possible. We surveyed the available GraphQL → SQL options and found some excellent candidates in Hasura and Graphile. Both support the set of features we're interested in, but consume significantly more memory than we could sacrifice on the free tier. Realizing that, we searched for an architecture that could meet our runtime constraints and performance requirements.
Our philosophy when adding to the stack is to use existing open source tools wherever possible. We surveyed the available GraphQL → SQL options and found some excellent candidates in Hasura and Graphile. Both support the set of features we're interested in, but consume significantly more memory than we could sacrifice on the free plan. Realizing that, we searched for an architecture that could meet our runtime constraints and performance requirements.
## Architecture
@@ -60,7 +60,7 @@ Somewhere around the 4,000 line count, things started to bog down. The rough sha
- SQL functions are “pass by value” (copy) so passing state around can get expensive
- The query planner tends to stop inlining functions when calls are deeply nested
Combine those two facts with our GraphQL AST being a big ball of JSONB, and the GitHub issues about slow schema introspection on free tier started to trickle in.
Combine those two facts with our GraphQL AST being a big ball of JSONB, and the GitHub issues about slow schema introspection on free plan started to trickle in.
Enter [pgx](https://github.com/tcdi/pgx)…
@@ -68,7 +68,7 @@ pgx is a rust framework for building Postgres extensions. It has a very polished
One of rust's main selling points is zero cost abstractions. That means high-level concepts like generics incur no runtime penalty. Transitioning from SQL's expensive abstractions to rust's zero cost abstractions has been hugely satisfying as it enabled refactoring the codebase into a more maintainable state. Development velocity is up. Code quality is up. Performance is WAY up.
As of 1.0, overhead introduced by pg_graphql is sub 300 *micro*seconds per request on free-tier hardware. When executed from SQL we see ~1060 queries per second per connection (no parallelism). If we include the entire Auth + HTTP stack, free tier can handle ~645 requests per second. On larger instance the stack handles upwards of 10k requests / second.
As of 1.0, overhead introduced by pg_graphql is sub 300 *micro*seconds per request on free-plan hardware. When executed from SQL we see ~1060 queries per second per connection (no parallelism). If we include the entire Auth + HTTP stack, free plan can handle ~645 requests per second. On larger instance the stack handles upwards of 10k requests / second.
```text
This is ApacheBench, Version 2.3 <$Revision: 1843412 $>
@@ -15,7 +15,7 @@ toc_depth: 3
When we announced Point-in-Time a few months ago, it was only available for Enterprise customers. Today, we're making PITR available for more projects, with a new Dashboard UI that makes it simple to use.
Pro-tier projects created from August 2022 onwards (excluding Northeast Asia / Seoul region), can now enable Point In Time Recovery (PITR) from the dashboard. Projects must have the "Small" compute add-on to ensure that PITR operates smoothly behind the scenes.
Pro-plan projects created from August 2022 onwards (excluding Northeast Asia / Seoul region), can now enable Point In Time Recovery (PITR) from the dashboard. Projects must have the "Small" compute add-on to ensure that PITR operates smoothly behind the scenes.
For older projects, don't worry - we're working on making it available for everybody. In the meantime, if you would like to have PITR enabled for your ineligible project, reach out directly [here](https://app.supabase.com/support/new), or stay tuned for further updates.
@@ -35,7 +35,7 @@ Under the hood, we use [WAL-G](https://github.com/wal-g/wal-g), an open source a
Consider your Recovery Point Objective (RPO) when deciding whether to enable Point in Time Recovery. RPO is the threshold for how much data, measured in time, a business could lose when disaster strikes. This is dependent on a business and its underlying requirements. The agreed upon RPO would be a deciding factor in choosing which solution best fits a project.
While all Pro-tier projects and above are backed up on a daily basis, this means that at the worst case, a project could lose up to 24 hours worth of data if disaster hits at the most inopportune time. With Point in Time Recovery however, backups are made at much shorter intervals, shortening the RPO. WAL files are backed up at two minute intervals. This could be faster if it hits a certain file threshold before the the two minute mark.
While all Pro-plan projects and above are backed up on a daily basis, this means that at the worst case, a project could lose up to 24 hours worth of data if disaster hits at the most inopportune time. With Point in Time Recovery however, backups are made at much shorter intervals, shortening the RPO. WAL files are backed up at two minute intervals. This could be faster if it hits a certain file threshold before the the two minute mark.
## Getting started
@@ -81,7 +81,7 @@ Privacy is becoming one of the most important features in a modern product. Supa
### Using the Vault
You'll see the Vault appear in the Dashboard over the next month. If you want to use Vault today, email growth@supabase.com and we'll enable it on your account. Vault is available for all customers (including the Free-tier).
You'll see the Vault appear in the Dashboard over the next month. If you want to use Vault today, email growth@supabase.com and we'll enable it on your account. Vault is available for all customers (including the Free-plan).
## More Launch Week 6
@@ -25,7 +25,7 @@ Resumable uploads divides the file into chunks before uploading them, emitting p
/>
</video>
With this release, users on the Pro tier or higher can now upload files as large as 50GB! This substantial upgrade from the previous limit of 5GB offers even more flexibility for your file uploads.
With this release, users on the Pro plan or higher can now upload files as large as 50GB! This substantial upgrade from the previous limit of 5GB offers even more flexibility for your file uploads.
To build this feature, we implemented Postgres Advisory locks which solved some gnarly concurrency problems. We can now handle edge-cases, like two clients uploading to the same location. We’ll deep dive into how we implemented Advisory locks later in the post.
@@ -24,7 +24,7 @@ With Single Sign-On (SSO), your users can login with their company's identity pr
Every developer building a B2B application eventually needs the SSO authentication flow to onboard enterprise customers. SSO is a requirement for larger Enterprise customers because it's a standard request in Enterprise Security Policies. Over the past few months, we've been [dogfooding SSO for our own Enterprise customers](https://supabase.com/docs/guides/platform/sso), and today we're releasing it for you to do the same.
Building SSO into your application isn't necessarily hard, but does come with some complexity. A lot of time can be spent understanding the nuances and details of the protocol - from dissecting the jargon to testing the implementation heavily. It took us months to build it for ourselves. With this release, you will have SSO set up and running in less than an hour so that you can focus on shipping the core features of your product. This feature is available for the [Pro-tier and above](https://supabase.com/pricing), starting today. This will also be available on the self-hosted version.
Building SSO into your application isn't necessarily hard, but does come with some complexity. A lot of time can be spent understanding the nuances and details of the protocol - from dissecting the jargon to testing the implementation heavily. It took us months to build it for ourselves. With this release, you will have SSO set up and running in less than an hour so that you can focus on shipping the core features of your product. This feature is available for the [Pro-plan and above](https://supabase.com/pricing), starting today. This will also be available on the self-hosted version.
<div className="video-container">
<iframe
+1 -1
View File
@@ -98,4 +98,4 @@ Migrating to Supabase helped the team at Mobbin instantly improve the end-user e
They now add new features with confidence, and they continue to help creators all over the globe find inspiration for their next project.
You can [check out Mobbin on their website](https://mobbin.design/browse/ios/apps).
[Sign up](https://app.supabase.com/) for Supabase's free tier and set up a scalable backend in less than 2 minutes.
[Sign up](https://app.supabase.com/) for Supabase's free plan and set up a scalable backend in less than 2 minutes.
+1 -1
View File
@@ -88,4 +88,4 @@ Replenysh clients include brands with global recognition and millions of custome
## Supabase help Replenysh continue to scale
Supabase turbo-charged Replenysh's development time with a seamless end-user mobile login experience. They are ready to help major brands engage with their users through participation in the circular economy. With Supabase, the team knows they have a slick auth experience, reduced DevOps overhead, and can continue to scale with Postgres. You can test out the Supabase developer experience today by [starting a new project on the free tier!.](https://app.supabase.com)
Supabase turbo-charged Replenysh's development time with a seamless end-user mobile login experience. They are ready to help major brands engage with their users through participation in the circular economy. With Supabase, the team knows they have a slick auth experience, reduced DevOps overhead, and can continue to scale with Postgres. You can test out the Supabase developer experience today by [starting a new project on the free plan!.](https://app.supabase.com)
+2 -1
View File
@@ -17,6 +17,7 @@ export interface CodeBlockProps {
className?: string
children?: string
size?: 'small' | 'medium' | 'large'
background?: string
}
function CodeBlock(props: CodeBlockProps) {
@@ -98,7 +99,7 @@ function CodeBlock(props: CodeBlockProps) {
padding: '21px 24px',
fontSize: large ? 18 : '0.875rem',
lineHeight: large ? 1.6 : 1.4,
background: '#181818',
background: props.background ?? '#1A1A1A',
}}
showLineNumbers={props.showLineNumbers}
lineNumberStyle={{
+8 -2
View File
@@ -44,6 +44,12 @@ const Features = () => {
telemetryProps,
router
)
case 'Vector':
return await Telemetry.sendEvent(
gaEvents['www_hp_subhero_products_vector'],
telemetryProps,
router
)
}
}
@@ -54,7 +60,7 @@ const Features = () => {
<div key={name} className="mb-10 space-y-4 md:mb-0">
<div className="flex items-center">
<ProductIcon icon={icon} />
<dt className="text-scale-1200 ml-4 flex flex-row xl:flex-col">{name}</dt>
<dt className="text-scale-1200 ml-3 flex flex-row xl:flex-col">{name}</dt>
</div>
<p className="p">{description}</p>
@@ -78,7 +84,7 @@ const Features = () => {
return (
<SectionContainer className="space-y-16 pb-0">
<h3 className="h3 text-center">Build faster and focus on your products</h3>
<dl className="grid grid-cols-1 gap-y-4 sm:grid-cols-2 md:grid-cols-2 md:gap-16 xl:grid-cols-5 lg:gap-x-8 xl:gap-x-12">
<dl className="grid grid-cols-1 gap-y-4 sm:grid-cols-2 md:grid-cols-2 md:gap-16 xl:grid-cols-6 lg:gap-x-8">
{IconSections}
</dl>
</SectionContainer>
+6 -2
View File
@@ -9,14 +9,18 @@ import * as supabaseLogoWordmarkDark from 'common/assets/images/supabase-logo-wo
import * as supabaseLogoWordmarkLight from 'common/assets/images/supabase-logo-wordmark--light.png'
import { useRouter } from 'next/router'
const Footer = () => {
interface Props {
className?: string
}
const Footer = (props: Props) => {
const { isDarkMode } = useTheme()
const { pathname } = useRouter()
const isLaunchWeekPage = pathname.includes('launch-week')
return (
<footer
className="border-scale-500 dark:border-scale-600 border-t"
className={['border-scale-500 dark:border-scale-600 border-t', props.className].join(' ')}
aria-labelledby="footerHeading"
>
<h2 id="footerHeading" className="sr-only">
@@ -1,10 +1,23 @@
import React, { PropsWithChildren, useEffect, useRef } from 'react'
interface Props {
outerClassName?: string
innerClassName?: string
hasActiveOnHover?: boolean
hasInnerShimmer?: boolean
shimmerFromColor?: string
shimmerToColor?: string
}
const InteractiveShimmerCard = ({
outerClassName,
innerClassName,
hasActiveOnHover = false,
hasInnerShimmer = false,
shimmerFromColor,
shimmerToColor,
children,
}: PropsWithChildren<{ outerClassName?: string; innerClassName?: string }>) => {
}: PropsWithChildren<Props>) => {
const outerRef = useRef(null)
const innerRef = useRef(null)
@@ -19,16 +32,20 @@ const InteractiveShimmerCard = ({
x = event.clientX - elX
y = event.clientY - elY
const isActive = x > -3 && x < width + 3 && y > -3 && y < height + 3
const activeGlow = isActive
? `radial-gradient(65rem circle at ${x}px ${y}px, var(--colors-brand9), transparent), `
: ''
const activeGlow =
hasActiveOnHover && isActive
? `radial-gradient(65rem circle at ${x}px ${y}px, var(--colors-brand9), transparent), `
: ''
outerElement.style.background = isActive ? `var(--colors-brand9)` : `var(--colors-scale3)`
outerElement.style.backgroundImage = `
${activeGlow}radial-gradient(30rem circle at ${x}px ${y}px, var(--colors-scale9), var(--colors-scale6))`
${activeGlow}radial-gradient(30rem circle at ${x}px ${y}px, ${
shimmerFromColor ?? 'var(--colors-scale8)'
}, ${shimmerToColor ?? 'var(--colors-scale3)'})`
innerElement.style.backgroundImage = isActive
? `radial-gradient(7rem circle at ${x}px ${y}px, var(--colors-scale5), transparent), radial-gradient(20rem circle at ${x}px ${y}px, var(--colors-scale4), transparent)`
: ''
innerElement.style.backgroundImage =
hasInnerShimmer && isActive
? `radial-gradient(7rem circle at ${x}px ${y}px, var(--colors-scale5), transparent), radial-gradient(20rem circle at ${x}px ${y}px, var(--colors-scale4), transparent)`
: ''
}
useEffect(() => {
@@ -44,13 +61,13 @@ const InteractiveShimmerCard = ({
<div
ref={outerRef}
className={[
'relative rounded-xl bg-slate-400 from-slate-800 to-slate-800 p-px transition-all shadow-md',
'relative rounded-xl bg-scale-400 from-scale-800 to-scale-800 p-px transition-all shadow-md',
outerClassName,
].join(' ')}
>
<div
className={[
'relative rounded-xl bg-scale-300 overflow-hidden transition-all flex flex-col text-slate-1100 lg:items-center lg:text-center',
'relative h-full rounded-xl bg-scale-200 dark:bg-scale-300 overflow-hidden transition-all text-scale-1100',
innerClassName,
].join(' ')}
>
@@ -34,7 +34,7 @@ export function TicketBrickWallSlider({ users, reverse, speed = 10000 }: Props)
return (
<div ref={ref} className="relative h-auto w-full m-0 overflow-hidden mb-2.5">
<div className="flex ticket-brick-swiper">
<div className="flex swiper-transition-linear">
<Swiper
onSwiper={(swiper) => setSwiperInstance(swiper)}
centeredSlides={true}
+11 -3
View File
@@ -5,19 +5,27 @@ import Footer from 'components/Footer/index'
type Props = {
hideHeader?: boolean
hideFooter?: boolean
className?: string
footerClassName?: string
children: React.ReactNode
}
const DefaultLayout = (props: Props) => {
const { hideHeader = false, hideFooter = false, children } = props
const {
hideHeader = false,
hideFooter = false,
className = '',
footerClassName = '',
children,
} = props
return (
<>
{!hideHeader && <Nav />}
<div className="min-h-screen">
<main>{children}</main>
<main className={className}>{children}</main>
</div>
{!hideFooter && <Footer />}
{!hideFooter && <Footer className={footerClassName} />}
</>
)
}
+164
View File
@@ -0,0 +1,164 @@
import { useRef } from 'react'
import Image from 'next/image'
import Link from 'next/link'
import {
MotionValue,
motion,
useInView,
useMotionValue,
useSpring,
useTransform,
} from 'framer-motion'
import { useBreakpoint } from 'common'
import { DEFAULT_TRANSITION } from '~/lib/animations'
import { Products } from './Sections/ProductsCta'
import { PRODUCT_NAMES, PRODUCT_SHORTNAMES } from '~/lib/constants'
function MagnifiedProducts({ currentProduct }: { currentProduct?: Products }) {
let mouseX = useMotionValue(Infinity)
return (
<motion.div
onMouseMove={(e) => mouseX.set(e.pageX)}
onMouseLeave={() => mouseX.set(Infinity)}
className="mx-auto w-full max-w-md grid grid-cols-3 md:flex items-center justify-center gap-y-8 md:gap-4 px-4"
>
{Object.entries(products).map(([key, product], i) => (
<Product
mouseX={mouseX}
product={product}
isCurrentProduct={product.shortname === currentProduct}
index={i}
key={key}
/>
))}
</motion.div>
)
}
function Product({
mouseX,
product,
index,
isCurrentProduct,
}: {
mouseX: MotionValue
product: any
index: number
isCurrentProduct: boolean
}) {
let ref = useRef<HTMLDivElement>(null)
const isInView = useInView(ref, { margin: '-25%', once: true })
const isMobile = useBreakpoint(768)
let distance = useTransform(mouseX, (val) => {
let bounds = ref.current?.getBoundingClientRect() ?? { x: 0, width: 0 }
return val - bounds.x - bounds.width / 2
})
let widthSync = useTransform(distance, [-150, 0, 150], [75, 110, 75])
let width = useSpring(widthSync, { mass: 0.1, stiffness: 150, damping: 12 })
const xDelta = 91
const initial = {
x: isMobile ? 0 : index * -xDelta + 225,
}
const animate = {
x: 0,
transition: { ...DEFAULT_TRANSITION, delay: 0.5 },
}
return (
<motion.div
ref={ref}
className={[
'relative mx-auto md:w-[150px] bg-transparent group',
isCurrentProduct ? 'z-10' : 'z-0',
].join(' ')}
initial={initial}
animate={isInView ? animate : initial}
>
<Link href={product.url}>
<a className="flex w-full flex-col items-center text-center">
<motion.div
style={isMobile ? (undefined as any) : { width, willChange: 'width' }}
className="relative w-[50px] aspect-square will-change-transform"
>
<Image
src={product.icon}
priority
layout="fill"
objectFit="contain"
lazyBoundary="100px"
/>
</motion.div>
<div className="text-brand-1100 dark:text-brand-900 flex justify-center relative opacity-70 md:absolute md:bottom-0 md:opacity-0 group-hover:opacity-100 transition-opacity md:translate-y-8 md:-left-20 md:md:-right-20 font-mono uppercase text-center text-xs mt-2">
<span>{product.name}</span>
</div>
</a>
</Link>
</motion.div>
)
}
const products = {
database: {
shortname: PRODUCT_SHORTNAMES.DATABASE,
name: PRODUCT_NAMES.DATABASE,
icon: '/images/product/database/database-v2.png',
description:
"Every project is a full Postgres database, the world's most trusted relational database.",
description_short: '',
label: '',
url: '/database',
},
authentication: {
shortname: PRODUCT_SHORTNAMES.AUTH,
name: PRODUCT_NAMES.AUTH,
icon: '/images/product/auth/auth-v2.png',
description: 'Add user sign ups and logins, securing your data with Row Level Security.',
description_short: '',
label: '',
url: '/auth',
},
storage: {
shortname: PRODUCT_SHORTNAMES.STORAGE,
name: PRODUCT_NAMES.STORAGE,
icon: '/images/product/storage/storage-v2.png',
description: 'Store, organize, and serve large files. Any media, including videos and images.',
description_short: '',
label: '',
url: '/storage',
},
'edge-functions': {
shortname: PRODUCT_SHORTNAMES.FUNCTIONS,
name: PRODUCT_NAMES.FUNCTIONS,
icon: '/images/product/functions/functions-v2.png',
description: 'Write custom code without deploying or scaling servers.',
description_short: '',
label: '',
url: '/edge-functions',
},
realtime: {
shortname: PRODUCT_SHORTNAMES.REALTIME,
name: PRODUCT_NAMES.REALTIME,
icon: '/images/product/realtime/realtime-v2.png',
description:
'Create multiplayer experiences by sharing, broadcasting, and listening to changes from other clients or the Database.',
description_short: '',
label: '',
url: '/realtime',
},
vector: {
shortname: PRODUCT_SHORTNAMES.VECTOR,
name: PRODUCT_NAMES.VECTOR,
icon: '/images/product/vector/vector-v2.png',
description: 'Integrate your favorite ML-models to store, index and search vector embeddings.',
description_short: '',
label: '',
url: '/vector',
},
}
export default MagnifiedProducts
@@ -73,7 +73,7 @@ export default function ComputePricingModal({ showComputeModal, setShowComputeMo
{i === 0 && (
<tr className="">
<td className="pb-1 bg-scale-700 px-3 py-1 -mr-1 border-l-4 border-scale-700">
<span className="">Included in Free and Pro tiers</span>
<span className="">Included in Free and Pro plan</span>
</td>
</tr>
)}
@@ -88,9 +88,9 @@ export default function ComputePricingModal({ showComputeModal, setShowComputeMo
<td key={column.key} className="p-3">
{column.key === 'dedicated' ? (
column.value ? (
<IconPricingIncludedCheck tier="Pro tier" />
<IconPricingIncludedCheck tier="Pro plan" />
) : (
<IconPricingMinus tier="Free tier" />
<IconPricingMinus tier="Free plan" />
)
) : (
column.value
@@ -124,9 +124,9 @@ export default function ComputePricingModal({ showComputeModal, setShowComputeMo
>
{column.key === 'dedicated' ? (
column.value ? (
<IconPricingIncludedCheck tier="Pro tier" />
<IconPricingIncludedCheck tier="Pro plan" />
) : (
<IconPricingMinus tier="Free tier" />
<IconPricingMinus tier="Free plan" />
)
) : (
column.value
@@ -0,0 +1,22 @@
import React, { ReactNode } from 'react'
import SectionContainer from '~/components/Layouts/SectionContainer'
interface Props {
title: string
image: string | ReactNode
}
const CenteredTitleImage = ({ title, image }: Props) => {
return (
<SectionContainer>
<div className="flex flex-col items-center text-center gap-8">
<h2 className="heading-gradient text-2xl sm:text-3xl xl:text-4xl">{title}</h2>
<div className="w-full max-w-5xl aspect-[2/1] md:aspect-[3/1] flex justify-center items-center mx-auto">
{image}
</div>
</div>
</SectionContainer>
)
}
export default CenteredTitleImage
@@ -0,0 +1,220 @@
import { useEffect, useRef, useState } from 'react'
import Image from 'next/image'
import { useRouter } from 'next/router'
import Link from 'next/link'
import { useWindowSize } from 'react-use'
import TweetCard from '~/components/TweetCard'
import { Swiper, SwiperSlide } from 'swiper/react'
import SwiperCore, { Autoplay } from 'swiper'
import 'swiper/swiper.min.css'
import SectionContainer from '../Layouts/SectionContainer'
import { useBreakpoint } from 'common'
SwiperCore.use([Autoplay])
interface CardInterface {
type: 'twitter' | 'customer-story'
avatar?: string
author: string
role?: string
quote: string | React.ReactNode
abstract?: string
image?: string
url?: string
logo?: string
customer?: string
}
const vectorImagesDir = '/images/product/vector/community/'
const cards: CardInterface[] = [
{
type: 'twitter',
avatar: '',
author: 'Yasser',
quote:
'@kiwicopple @supabase @PostgreSQL @OpenAI Adding vector embeddings support to @Supabase is awesome. Glad I built https://t.co/jnCYOLa4iK on supabase.',
},
{
type: 'customer-story',
avatar: '',
customer: 'Markprompt',
author: 'Michael Fester',
role: 'Co-Founder, Markprompt',
quote:
'We decided to use Supabase over other specialized vector databases because it enabled us to be GDPR compliant from day one with little effort.',
image: vectorImagesDir + 'supabase+mendable.svg',
abstract: 'Markprompt and Supabase - GDPR-Compliant AI Chatbots for Docs and Websites.',
url: '/customers/markprompt',
},
{
type: 'customer-story',
avatar: '',
customer: 'Mendable',
author: 'Caleb Peffer',
role: 'CEO, Mendable',
quote:
'We tried other vector databases - we tried Faiss, we tried Weviate, we tried Pinecone. We found them to be incredibly expensive and not very intuitive. If you’re just doing vector search they’re great, but if you need to store a bunch of metadata that becomes a huge pain.',
image: vectorImagesDir + 'supabase+markprompt.svg',
abstract: 'Mendable switches from Pinecone to Supabase for PostgreSQL vector embeddings.',
url: '/customers/mendable',
},
{
type: 'twitter',
avatar: '',
author: 'Batuhan',
quote: (
<>
To create long-term memory for your ChatGPT application you can use @supabase vector
database.
<br />
You can fix two common problems with this method:
<br />
Global Memory Token Size Limit Handling
</>
),
},
]
const CommunitySlider = () => {
const ref = useRef<any>(null)
const isSm = useBreakpoint(768)
const { basePath } = useRouter()
const [swiperInstance, setSwiperInstance] = useState<SwiperCore | any | null>(null)
const { width } = useWindowSize()
useEffect(() => {
// trigger autoplay if viewport resize
if (swiperInstance) swiperInstance?.autoplay?.start()
}, [width])
useEffect(() => {
if (!ref.current || !swiperInstance?.autoplay) return
ref.current?.addEventListener('mouseover', () => swiperInstance?.autoplay?.stop())
ref.current?.addEventListener('mouseleave', () => swiperInstance?.autoplay?.start())
return () => {
ref.current?.removeEventListener('mouseover', () => swiperInstance?.autoplay?.stop())
ref.current?.removeEventListener('mouseleave', () => swiperInstance?.autoplay?.start())
}
}, [ref.current, swiperInstance?.autoplay])
const Card = (card: CardInterface) => (
<div className="dark:bg-scale-300 hover:border-scale-600 hover:dark:border-scale-700 border-scale-300 dark:border-scale-400 rounded-2xl border bg-white p-6 drop-shadow-sm flex flex-col gap-4">
<div className="flex items-center gap-2">
<div className="h-10 w-10 overflow-hidden rounded-full border dark:border-gray-600">
<Image src={card.avatar!} layout="responsive" width="64" height="64" alt={card.author} />
</div>
<div>
<p className="text-scale-1200 font-medium">{card.author}</p>
<p className="text-scale-900 text-sm">{card.role}</p>
</div>
</div>
<p className="text-scale-900 mt-2 text-base">{card.quote}</p>
<div className="mt-3 pt-3 border-t">
<div className="relative h-10">
<Image
src={card.image!}
alt={`Supabase + ${card.customer}`}
layout="fill"
objectFit="contain"
objectPosition="left"
/>
</div>
</div>
<p className="text-scale-900 my-3 text-base">{card.abstract}</p>
{card.url && (
<div className="text-brand-900 block cursor-pointer text-sm">Read Customer Story</div>
)}
</div>
)
return (
<div className="py-16 sm:py-18 md:py-24 bg-scale-100">
<div className="relative h-[300px] w-screen mx-auto md:w-full -mb-32 z-0">
<Image
src="/images/product/vector/community/vector-community.svg"
alt="vector graphic"
layout="fill"
objectFit={isSm ? 'cover' : 'contain'}
/>
</div>
<SectionContainer className="!py-0">
<div className="col-span-12 text-center relative z-10">
<h3 className="text-3xl md:text-4xl heading-gradient">
Join a growing <br />
community of users
</h3>
</div>
</SectionContainer>
<div ref={ref} className="relative mx-auto pt-16 pb-0 swiper-transition-linear">
<Swiper
onSwiper={(swiper) => setSwiperInstance(swiper)}
centeredSlides={true}
spaceBetween={30}
slidesPerView={3}
speed={13000}
loop={true}
watchOverflow
threshold={2}
updateOnWindowResize
allowTouchMove={false}
autoplay={{
delay: 0,
disableOnInteraction: true,
reverseDirection: false,
}}
breakpoints={{
320: {
slidesPerView: 1.5,
spaceBetween: 10,
},
720: {
slidesPerView: 2.5,
spaceBetween: 20,
},
1280: {
slidesPerView: 4,
spaceBetween: 30,
},
1700: {
slidesPerView: 5,
spaceBetween: 30,
},
2100: {
slidesPerView: 6,
spaceBetween: 30,
},
}}
>
{cards.map((card, i) => (
<SwiperSlide key={card.author}>
{card.type === 'twitter' ? (
<TweetCard
handle={`@${card.author}`}
quote={card.quote}
img_url={`${basePath}${card.avatar}`}
/>
) : (
<Link href={`${basePath}${card.url}`}>
<a>
<Card {...card} />
</a>
</Link>
)}
</SwiperSlide>
))}
</Swiper>
</div>
</div>
)
}
export default CommunitySlider
@@ -0,0 +1,115 @@
import Image from 'next/image'
import Link from 'next/link'
import { useRouter } from 'next/router'
import { useTheme } from 'common/Providers'
import { IconArrowUpRight } from 'ui'
import { LazyMotion, domAnimation, m, useInView } from 'framer-motion'
import SectionContainer from '~/components/Layouts/SectionContainer'
import CustomersVisual from '~/components/products/vector/CustomersVisual'
import { useRef } from 'react'
import { INITIAL_BOTTOM, getAnimation } from '~/lib/animations'
interface Customer {
type?: string
avatar?: string
author?: string
role?: string
quote?: string | React.ReactNode
abstract?: string
image?: string
url?: string
logo?: string
customer?: string
}
interface Card {
customer: any
index: number
}
interface Props {
title: string | React.ReactNode
customers: Customer[]
secondaryLinks: Customer[]
}
const CustomerQuotesSection = (props: Props) => {
const { basePath } = useRouter()
const ref = useRef(null)
const isInView = useInView(ref, { margin: '-25%', once: true })
const Card = ({ customer, index }: Card) => {
const { isDarkMode } = useTheme()
const logo = `/images/customers/logos/${!isDarkMode ? '' : 'light/'}${customer.customer}.png`
const initial = INITIAL_BOTTOM
const animate = getAnimation({ delay: index * 0.1 })
return (
<m.div
initial={initial}
animate={isInView ? animate : initial}
className="bg-scale-100 dark:bg-scale-200 hover:border-scale-600 hover:dark:border-scale-700 border-scale-300 dark:border-scale-400 rounded-2xl border p-6 drop-shadow-sm flex flex-col justify-between"
>
<div className="flex flex-col gap-4">
<div className="h-24 w-full flex items-center justify-center pb-3">
<div className="relative w-full flex items-center justify-center h-10">
<Image
src={logo}
alt={`Supabase + ${customer.customer}`}
layout="fill"
objectFit="contain"
objectPosition="center"
/>
</div>
</div>
<div className="border-t pt-4">
<blockquote className="text-scale-1100 text-base">{customer.quote}</blockquote>
</div>
</div>
<div>
<p className="text-scale-900 mt-4">
{customer.author}, {customer.role}
</p>
{customer.url && (
<div className="text-brand-900 border-t mt-4 pt-4 cursor-pointer text-sm flex items-center justify-between">
<span>Read Customer Story</span>
<IconArrowUpRight />
</div>
)}
</div>
</m.div>
)
}
return (
<LazyMotion features={domAnimation}>
<div className="py-16 sm:py-18 md:py-24 overflow-hidden">
<div className="relative h-[300px] w-[300vw] left-[-98vw] md:left-0 mx-auto md:w-full -mb-32 z-0">
<CustomersVisual />
</div>
<SectionContainer className="!py-0">
<div className="col-span-12 text-center relative z-10">
<h3 className="text-3xl md:text-4xl heading-gradient">{props.title}</h3>
</div>
<div
ref={ref}
className="relative mx-auto max-w-4xl grid grid-cols-1 lg:grid-cols-2 gap-4 xl:gap-8 pt-16 pb-0"
>
{props.customers.map((customer, i: number) => (
<Link href={`${basePath}${customer.url}`} key={customer.customer}>
<a className="h-full flex">
<Card customer={customer} index={i} />
</a>
</Link>
))}
</div>
</SectionContainer>
</div>
</LazyMotion>
)
}
export default CustomerQuotesSection
@@ -0,0 +1,30 @@
import Link from 'next/link'
import React from 'react'
import { Button } from 'ui'
import SectionContainer from '~/components/Layouts/SectionContainer'
const EnterpriseCta = () => {
return (
<SectionContainer>
<div className="flex flex-col text-center gap-4 py-8 items-center justify-center">
<h2 className="heading-gradient text-2xl sm:text-3xl xl:text-4xl">
Supabase Vector for Enterprise
</h2>
<p className="mx-auto text-scale-900 w-full">
Talk to one of our experts about scaling Supabase Vector{' '}
<br className="hidden md:block" />
and managing embeddings at scale.
</p>
<div className="w-full mt-4 flex items-center justify-center text-center gap-4">
<Link href="https://forms.supabase.com/enterprise">
<a>
<Button size="medium">Fill out Enterprise Form</Button>
</a>
</Link>
</div>
</div>
</SectionContainer>
)
}
export default EnterpriseCta
@@ -0,0 +1,107 @@
import Link from 'next/link'
import React, { useRef, ReactNode } from 'react'
import { LazyMotion, domAnimation, m, useInView } from 'framer-motion'
import SectionContainer from '~/components/Layouts/SectionContainer'
import { INITIAL_BOTTOM, getAnimation } from '~/lib/animations'
import { ReactMarkdown } from 'react-markdown/lib/react-markdown'
import { Button, IconArrowUpRight } from 'ui'
interface Feature {
icon: string
title: string
text: string
}
interface Props {
title: string | ReactNode
paragraph: string
cta?: {
label?: string
link: string
}
features: Feature[]
}
const FeaturesSection = ({ title, paragraph, cta, features }: Props) => {
const ref = useRef(null)
const isInView = useInView(ref, { margin: '-25%', once: true })
return (
<LazyMotion features={domAnimation}>
<SectionContainer>
<div className="grid grid-cols-1 lg:grid-cols-12 gap-8 xl:gap-10 justify-between">
<div className="col-span-full lg:col-span-4 gap-2 flex flex-col">
<h2 className="text-2xl sm:text-3xl xl:text-4xl max-w-[280px] sm:max-w-xs xl:max-w-[360px] tracking-[-1px]">
{title}
</h2>
<p className="text-scale-900 mb-4">{paragraph}</p>
{cta && (
<Link href={cta.link}>
<a>
<Button type="default" size="small" icon={<IconArrowUpRight />}>
{cta.label ?? 'Explore documentation'}
</Button>
</a>
</Link>
)}
</div>
<div
ref={ref}
className="col-span-full lg:col-start-6 lg:col-span-7 space-y-10 lg:space-y-0 flex flex-col lg:grid lg:grid-cols-2 lg:gap-16"
>
{features.map((feature: Feature, i: number) => (
<Feature feature={feature} index={i} isInView={isInView} key={feature.title} />
))}
</div>
</div>
</SectionContainer>
</LazyMotion>
)
}
const Feature = ({
feature,
index,
isInView,
}: {
feature: Feature
index: number
isInView: boolean
}) => {
const initial = INITIAL_BOTTOM
const animate = getAnimation({ delay: index * 0.1 })
return (
<m.div
className="h-full flex items-start space-x-3 w-full"
initial={initial}
animate={isInView ? animate : initial}
>
<div className="flex items-center">
<div className="relative w-full h-6 flex items-center mx-auto">
<svg
width="25"
height="25"
viewBox="0 0 25 25"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d={feature.icon}
stroke="var(--colors-scale12)"
strokeMiterlimit="10"
strokeLinejoin="round"
strokeLinecap="round"
strokeWidth="1.5"
/>
</svg>
</div>
</div>
<div className="text-sm lg:text-base">
<h2 className="text-base">{feature.title}</h2>
<ReactMarkdown className="prose pt-1 text-sm text-scale-900">{feature.text}</ReactMarkdown>
</div>
</m.div>
)
}
export default FeaturesSection
@@ -0,0 +1,67 @@
import React, { useRef, useState } from 'react'
import { LazyMotion, domAnimation, m, useInView } from 'framer-motion'
import SectionContainer from '~/components/Layouts/SectionContainer'
import InteractiveShimmerCard from '~/components/InteractiveShimmerCard'
import { INITIAL_BOTTOM, getAnimation } from '~/lib/animations'
import Image from 'next/image'
interface Highlight {
image?: React.ReactNode
svg?: React.ReactNode
title: string
paragraph: string | React.ReactNode
}
const HighlightCards = ({ highlights }: { highlights: Highlight[] }) => {
return (
<SectionContainer>
<LazyMotion features={domAnimation}>
<div className="grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-4 gap-4">
{highlights.map((highlight, i) => (
<HighlightCard highlight={highlight} index={i} key={highlight.title} />
))}
</div>
</LazyMotion>
</SectionContainer>
)
}
const HighlightCard = ({ highlight, index }: { highlight: Highlight; index: number }) => {
const ref = useRef(null)
const [isHovered, setIsHovered] = useState(false)
const isInView = useInView(ref, { once: true })
const initial = INITIAL_BOTTOM
const animate = getAnimation({ delay: 0.4 + index * 0.1 })
const Img: any = highlight.image
return (
<m.div
ref={ref}
initial={initial}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
animate={isInView ? animate : initial}
>
<InteractiveShimmerCard innerClassName="flex flex-col !bg-scale-100">
<div className="relative w-full aspect-[1.35/1] mb-4">
<div
className="absolute inset-0 w-full h-full z-10"
style={{
background: `radial-gradient(100% 50% at 50% 50%, transparent, var(--colors-scale1))`,
}}
/>
{highlight.image && <Img isHovered={isHovered} />}
{highlight.svg && highlight.svg}
</div>
<div className="p-8">
<h3 className="text-lg text-scale-1200 mb-2">{highlight.title}</h3>
<p className="text-scale-900">{highlight.paragraph}</p>
</div>
</InteractiveShimmerCard>
</m.div>
)
}
export default HighlightCards
Loaded 100 of 232 files, more files were not shown because too many files have changed in this diff. Show more