# llms-full (private-aware)
> Built from GitHub files and website pages. Large files may be truncated.

--- apps/docs/content/guides/getting-started/tutorials/with-angular.mdx ---
---
title: 'Build a User Management App with Angular'
description: 'Learn how to use Supabase in your Angular App.'
---

<$Partial path="quickstart_intro.mdx" />

![Supabase User Management example](/docs/img/user-management-demo.png)

<Admonition type="note">

If you get stuck while working through this guide, refer to the [full example on GitHub](https://github.com/supabase/supabase/tree/master/examples/user-management/angular-user-management).

</Admonition>

<$Partial path="project_setup.mdx" variables={{ "framework": "ionicangular", "tab": "mobiles" }} />

## Building the app

Start with building the Angular app from scratch.

### Initialize an Angular app

You can use the [Angular CLI](https://angular.io/cli) to initialize
an app called `supabase-angular`. The command sets some defaults, that you change to suit your needs:

```bash
npx ng new supabase-angular --routing false --style css --standalone false --zoneless true --ssr false
cd supabase-angular
```

Then, install the only additional dependency: [supabase-js](https://github.com/supabase/supabase-js)

```bash
npm install @supabase/supabase-js
```

Finally, save the environment variables in the `src/environments/environment.ts` file.
All you need are the API URL and the key that you copied [earlier](#get-api-details).
The application exposes these variables in the browser, and that's fine as you have [Row Level Security](/docs/guides/auth#row-level-security) enabled on the Database.

<$CodeTabs>

```ts name=src/environments/environment.ts
export const environment = {
  production: false,
  supabaseUrl: 'YOUR_SUPABASE_URL',
  supabaseKey: 'YOUR_SUPABASE_KEY',
}
```

</$CodeTabs>

Now you have the API credentials in place, create a `SupabaseService` with `ng g s supabase` and add the following code to initialize the Supabase client and implement functions to communicate with the Supabase API.

<$CodeTabs>

```ts name=src/app/supabase.service.ts
import { Injectable } from '@angular/core'
import {
  AuthChangeEvent,
  AuthSession,
  createClient,
  Session,
  SupabaseClient,
  User,
} from '@supabase/supabase-js'
import { environment } from '../environments/environment'

export interface Profile {
  id?: string
  username: string
  website: string
  avatar_url: string
}

@Injectable({
  providedIn: 'root',
})
export class SupabaseService {
  private supabase: SupabaseClient
  _session: AuthSession | null = null

  constructor() {
    this.supabase = createClient(environment.supabaseUrl, environment.supabaseKey)
  }

  get session() {
    this.supabase.auth.getSession().then(({ data }) => {
      this._session = data.session
    })
    return this._session
  }

  profile(user: User) {
    return this.supabase
      .from('profiles')
      .select(`username, website, avatar_url`)
      .eq('id', user.id)
      .single()
  }

  authChanges(callback: (event: AuthChangeEvent, session: Session | null) => void) {
    return this.supabase.auth.onAuthStateChange(callback)
  }

  signIn(email: string) {
    return this.supabase.auth.signInWithOtp({ email })
  }

  signOut() {
    return this.supabase.auth.signOut()
  }

  updateProfile(profile: Profile) {
    const update = {
      ...profile,
      updated_at: new Date(),
    }

    return this.supabase.from('profiles').upsert(update)
  }

  downLoadImage(path: string) {
    return this.supabase.storage.from('avatars').download(path)
  }

  uploadAvatar(filePath: string, file: File) {
    return this.supabase.storage.from('avatars').upload(filePath, file)
  }
}
```

</$CodeTabs>

Optionally, update `src/styles.css` [with the following styles](https://raw.githubusercontent.com/supabase/supabase/master/examples/user-management/angular-user-management/src/styles.css) to style the app.

### Set up a login component

Next, set up an Angular component to manage logins and sign ups. The component uses [Magic Links](/docs/guides/auth/auth-email-passwordless#with-magic-link), so users can sign in with their email without using passwords.

Create an `AuthComponent` with the `ng g c auth` Angular CLI command and add the following code.

<$CodeTabs>

```ts name=src/app/auth/auth.ts
import { Component } from '@angular/core'
import { FormBuilder, FormGroup } from '@angular/forms'
import { SupabaseService } from '../supabase.service'

@Component({
  selector: 'app-auth',
  templateUrl: './auth.html',
  styleUrls: ['./auth.css'],
  standalone: false,
})
export class AuthComponent {
  signInForm!: FormGroup
  constructor(
    private readonly supabase: SupabaseService,
    private readonly formBuilder: FormBuilder
  ) {}

  loading = false
  ngOnInit() {
    this.signInForm = this.formBuilder.group({
      email: '',
    })
  }

  async onSubmit(): Promise<void> {
    try {
      this.loading = true
      const email = this.signInForm.value.email as string
      const { error } = await this.supabase.signIn(email)
      if (error) throw error
      alert('Check your email for the login link!')
    } catch (error) {
      if (error instanceof Error) {
        alert(error.message)
      }
    } finally {
      this.signInForm.reset()
      this.loading = false
    }
  }
}
```

```html name=src/app/auth/auth.html
<div class="row flex-center flex">
  <div class="col-6 form-widget" aria-live="polite">
    <h1 class="header">Supabase + Angular</h1>
    <p class="description">Sign in via magic link with your email below</p>
    <form [formGroup]="signInForm" (ngSubmit)="onSubmit()" class="form-widget">
      <div>
        <label for="email">Email</label>
        <input
          id="email"
          formControlName="email"
          class="inputField"
          type="email"
          placeholder="Your email"
        />
      </div>
      <div>
        <button type="submit" class="button block" [disabled]="loading">
          {{ loading ? "Loading" : "Send magic link" }}
        </button>
      </div>
    </form>
  </div>
</div>
```

</$CodeTabs>

### Account page

Users also need a way to edit their profile details and manage their accounts after signing in.
Create an `AccountComponent` with the `ng g c account` Angular CLI command and add the following code.

<$CodeTabs>

```ts name=src/app/account/account.ts
import { Component, Input, OnInit } from '@angular/core'
import { FormBuilder, FormGroup } from '@angular/forms'
import { AuthSession } from '@supabase/supabase-js'
import { Profile, SupabaseService } from '../supabase.service'

@Component({
  selector: 'app-account',
  templateUrl: './account.html',
  styleUrls: ['./account.css'],
  standalone: false,
})
export class AccountComponent implements OnInit {
  loading = false
  profile!: Profile
  updateProfileForm!: FormGroup

  get avatarUrl() {
    return this.updateProfileForm.value.avatar_url as string
  }
  async updateAvatar(event: string): Promise<void> {
    this.updateProfileForm.patchValue({
      avatar_url: event,
    })
    await this.updateProfile()
  }

  @Input()
  session!: AuthSession

  constructor(
    private readonly supabase: SupabaseService,
    private formBuilder: FormBuilder
  ) {
    this.updateProfileForm = this.formBuilder.group({
      username: '',
      website: '',
      avatar_url: '',
    })
  }

  async ngOnInit(): Promise<void> {
    await this.getProfile()

    const { username, website, avatar_url } = this.profile
    this.updateProfileForm.patchValue({
      username,
      website,
      avatar_url,
    })
  }

  async getProfile() {
    try {
      this.loading = true
      const { user } = this.session
      const { data: profile, error, status } = await this.supabase.profile(user)

      if (error && status !== 406) {
        throw error
      }

      if (profile) {
        this.profile = profile
      }
    } catch (error) {
      if (error instanceof Error) {
        alert(error.message)
      }
    } finally {
      this.loading = false
    }
  }

  async updateProfile(): Promise<void> {
    try {
      this.loading = true
      const { user } = this.session

      const username = this.updateProfileForm.value.username as string
      const website = this.updateProfileForm.value.website as string
      const avatar_url = this.updateProfileForm.value.avatar_url as string

      const { error } = await this.supabase.updateProfile({
        id: user.id,
        username,
        website,
        avatar_url,
      })
      if (error) throw error
    } catch (error) {
      if (error instanceof Error) {
        alert(error.message)
      }
    } finally {
      this.loading = false
    }
  }

  async signOut() {
    await this.supabase.signOut()
  }
}
```

```html name=src/app/account/account.html
<form [formGroup]="updateProfileForm" (ngSubmit)="updateProfile()" class="form-widget">
  <app-avatar [avatarUrl]="this.avatarUrl" (upload)="updateAvatar($event)"> </app-avatar>
  <div>
    <label for="email">Email</label>
    <input id="email" type="text" [value]="session.user.email" disabled />
  </div>
  <div>
    <label for="username">Name</label>
    <input formControlName="username" id="username" type="text" />
  </div>
  <div>
    <label for="website">Website</label>
    <input formControlName="website" id="website" type="url" />
  </div>

  <div>
    <button type="submit" class="button primary block" [disabled]="loading">
      {{ loading ? "Loading ..." : "Update" }}
    </button>
  </div>

  <div>
    <button class="button block" (click)="signOut()">Sign Out</button>
  </div>
</form>
```

</$CodeTabs>

### Launch!

Now you have all the components in place, update `AppComponent`:

<$CodeTabs>

```ts name=src/app/app.ts
import { Component, OnInit } from '@angular/core'
import { SupabaseService } from './supabase.service'

@Component({
  selector: 'app-root',
  templateUrl: './app.html',
  styleUrls: ['./app.css'],
  standalone: false,
})
export class AppComponent implements OnInit {
  constructor(private readonly supabase: SupabaseService) {}

  title = 'angular-user-management'
  session: any

  ngOnInit() {
    this.session = this.supabase.session
    this.supabase.authChanges((_, session) => (this.session = session))
  }
}
```

```html name=src/app/app.html
<div class="container" style="padding: 50px 0 100px 0">
  <app-account *ngIf="session; else auth" [session]="session"></app-account>
  <ng-template #auth>
    <app-auth></app-auth>
  </ng-template>
</div>
```

</$CodeTabs>

You also need to change `app.module.ts` to include the `ReactiveFormsModule` from the `@angular/forms` package.

<$CodeTabs>

```ts name=src/app/app.module.ts
import { NgModule } from '@angular/core'
import { BrowserModule } from '@angular/platform-browser'

import { AppComponent } from './app'
import { AuthComponent } from './auth/auth'
import { AccountComponent } from './account/account'
import { ReactiveFormsModule } from '@angular/forms'
import { AvatarComponent } from './avatar/avatar'

@NgModule({
  declarations: [AppComponent, AuthComponent, AccountComponent, AvatarComponent],
  imports: [BrowserModule, ReactiveFormsModule],
  providers: [],
  bootstrap: [AppComponent],
  exports: [AppComponent, AuthComponent, AccountComponent, AvatarComponent],
})
export class AppModule {}
```

</$CodeTabs>

Once that's done, run the application in a terminal:

```bash
npm run start
```

Open the browser to [localhost:4200](http://localhost:4200) and you should see the completed app.

![Screenshot of the Supabase Angular application running in a browser](/docs/img/supabase-angular-demo.png)

## Bonus: Profile photos

Every Supabase project is configured with [Storage](/docs/guides/storage) for managing large files like photos and videos.

### Create an upload widget

Create an avatar for the user so that they can upload a profile photo.
Create an `AvatarComponent` with `ng g c avatar` Angular CLI command and add the following code.

<$CodeTabs>

```ts name=src/app/avatar/avatar.ts
import { Component, EventEmitter, Input, Output } from '@angular/core'
import { SafeResourceUrl, DomSanitizer } from '@angular/platform-browser'
import { SupabaseService } from '../supabase.service'

@Component({
  selector: 'app-avatar',
  templateUrl: './avatar.html',
  styleUrls: ['./avatar.css'],
  standalone: false,
})
export class AvatarComponent {
  _avatarUrl: SafeResourceUrl | undefined
  uploading = false

  @Input()
  set avatarUrl(url: string | null) {
    if (url) {
      this.downloadImage(url)
    }
  }

  @Output() upload = new EventEmitter<string>()

  constructor(
    private readonly supabase: SupabaseService,
    private readonly dom: DomSanitizer
  ) {}

  async downloadImage(path: string) {
    try {
      const { data } = await this.supabase.downLoadImage(path)
      if (data instanceof Blob) {
        this._avatarUrl = this.dom.bypassSecurityTrustResourceUrl(URL.createObjectURL(data))
      }
    } catch (error) {
      if (error instanceof Error) {
        console.error('Error downloading image: ', error.message)
      }
    }
  }

  async uploadAvatar(event: any) {
    try {
      this.uploading = true
      if (!event.target.files || event.target.files.length === 0) {
        throw new Error('You must select an image to upload.')
      }

      const file = event.target.files[0]
      const fileExt = file.name.split('.').pop()
      const filePath = `${Math.random()}.${fileExt}`

      await this.supabase.uploadAvatar(filePath, file)
      this.upload.emit(filePath)
    } catch (error) {
      if (error instanceof Error) {
        alert(error.message)
      }
    } finally {
      this.uploading = false
    }
  }
}
```

```html name=src/app/avatar/avatar.html
<div>
  <img
    *ngIf="_avatarUrl"
    [src]="_avatarUrl"
    alt="Avatar"
    class="avatar image"
    style="height: 150px; width: 150px"
  />
</div>
<div *ngIf="!_avatarUrl" class="avatar no-image" style="height: 150px; width: 150px"></div>
<div style="width: 150px">
  <label class="button primary block" for="single">
    {{ uploading ? "Uploading ..." : "Upload" }}
  </label>
  <input
    style="visibility: hidden; position: absolute"
    type="file"
    id="single"
    accept="image/*"
    (change)="uploadAvatar($event)"
    [disabled]="uploading"
  />
</div>
```

</$CodeTabs>

### Add the new widget

And then we can add the widget on top of the `AccountComponent` HTML template:

<$CodeTabs>

```html name=src/app/account.html
<form [formGroup]="updateProfileForm" (ngSubmit)="updateProfile()" class="form-widget">
  <app-avatar [avatarUrl]="this.avatarUrl" (upload)="updateAvatar($event)"></app-avatar>
  <!-- input fields -->
</form>
```

</$CodeTabs>

And add an `updateAvatar` function along with an `avatarUrl` getter to the `AccountComponent` typescript file:

<$CodeTabs>

```ts name=src/app/account.ts
@Component({
  selector: 'app-account',
  templateUrl: './account.html',
  styleUrls: ['./account.css'],
})
export class AccountComponent implements OnInit {
  // ...
  get avatarUrl() {
    return this.updateProfileForm.value.avatar_url as string
  }

  async updateAvatar(event: string): Promise<void> {
    this.updateProfileForm.patchValue({
      avatar_url: event,
    })
    await this.updateProfile()
  }
  // ...
}
```

</$CodeTabs>

At this stage you have a fully functional application!


## Links discovered
- [Supabase User Management example](https://github.com/supabase/supabase/blob/master/docs/img/user-management-demo.png)
- [full example on GitHub](https://github.com/supabase/supabase/tree/master/examples/user-management/angular-user-management)
- [Angular CLI](https://angular.io/cli)
- [supabase-js](https://github.com/supabase/supabase-js)
- [Row Level Security](https://github.com/supabase/supabase/blob/master/docs/guides/auth#row-level-security.md)
- [with the following styles](https://raw.githubusercontent.com/supabase/supabase/master/examples/user-management/angular-user-management/src/styles.css)
- [Magic Links](https://github.com/supabase/supabase/blob/master/docs/guides/auth/auth-email-passwordless#with-magic-link.md)
- [localhost:4200](http://localhost:4200)
- [Screenshot of the Supabase Angular application running in a browser](https://github.com/supabase/supabase/blob/master/docs/img/supabase-angular-demo.png)
- [Storage](https://github.com/supabase/supabase/blob/master/docs/guides/storage.md)

--- apps/docs/content/guides/getting-started/tutorials/with-expo-react-native.mdx ---
---
title: 'Build a User Management App with Expo React Native'
description: 'Learn how to use Supabase in your React Native App.'
tocVideo: 'AE7dKIKMJy4'
---

<$Partial path="quickstart_intro.mdx" />

![Supabase User Management example](/docs/img/supabase-flutter-demo.png)

<Admonition type="note">

If you get stuck while working through this guide, refer to the [full example on GitHub](https://github.com/supabase/supabase/tree/master/examples/user-management/expo-user-management).

</Admonition>

<$Partial path="project_setup.mdx" variables={{ "framework": "exporeactnative", "tab": "mobiles" }} />

## Building the app

Let's start building the React Native app from scratch.

### Initialize a React Native app

We can use [`expo`](https://docs.expo.dev/get-started/create-a-new-app/) to initialize
an app called `expo-user-management`:

```bash
npx create-expo-app -t expo-template-blank-typescript expo-user-management

cd expo-user-management
```

Then let's install the additional dependencies: [supabase-js](https://github.com/supabase/supabase-js)

```bash
npx expo install @supabase/supabase-js @react-native-async-storage/async-storage @rneui/themed
```

Now let's create a helper file to initialize the Supabase client.
We need the API URL and the key that you copied [earlier](#get-api-details).
These variables are safe to expose in your Expo app since Supabase has
[Row Level Security](/docs/guides/database/postgres/row-level-security) enabled on your Database.

<Tabs
  scrollable
  size="large"
  type="underlined"
  defaultActiveId="async-storage"
  queryGroup="auth-store"
>
  <TabPanel id="async-storage" label="AsyncStorage">

    <$CodeTabs>

    ```ts name=lib/supabase.ts
    import AsyncStorage from '@react-native-async-storage/async-storage'
    import { createClient } from '@supabase/supabase-js'

    const supabaseUrl = YOUR_REACT_NATIVE_SUPABASE_URL
    const supabasePublishableKey = YOUR_REACT_NATIVE_SUPABASE_PUBLISHABLE_KEY

    export const supabase = createClient(supabaseUrl, supabasePublishableKey, {
      auth: {
        storage: AsyncStorage,
        autoRefreshToken: true,
        persistSession: true,
        detectSessionInUrl: false,
      },
    })
    ```

    </$CodeTabs>

  </TabPanel>
  <TabPanel id="secure-store" label="SecureStore">

    If you wish to encrypt the user's session information, you can use `aes-js` and store the encryption key in [Expo SecureStore](https://docs.expo.dev/versions/latest/sdk/securestore). The [`aes-js` library](https://github.com/ricmoo/aes-js) is a reputable JavaScript-only implementation of the AES encryption algorithm in CTR mode. A new 256-bit encryption key is generated using the `react-native-get-random-values` library. This key is stored inside Expo's SecureStore, while the value is encrypted and placed inside AsyncStorage.

    Make sure that:
    - You keep the `expo-secure-storage`, `aes-js` and `react-native-get-random-values` libraries up-to-date.
    - Choose the correct [`SecureStoreOptions`](https://docs.expo.dev/versions/latest/sdk/securestore/#securestoreoptions) for your app's needs. E.g. [`SecureStore.WHEN_UNLOCKED`](https://docs.expo.dev/versions/latest/sdk/securestore/#securestorewhen_unlocked) regulates when the data can be accessed.
    - Carefully consider optimizations or other modifications to the above example, as those can lead to introducing subtle security vulnerabilities.

    Install the necessary dependencies in the root of your Expo project:

    ```bash
    npm install @supabase/supabase-js
    npm install @rneui/themed @react-native-async-storage/async-storage
    npm install aes-js react-native-get-random-values
    npm install --save-dev @types/aes-js
    npx expo install expo-secure-store
    ```

    Implement a `LargeSecureStore` class to pass in as Auth storage for the `supabase-js` client:

    <$CodeTabs>

    ```ts name=lib/supabase.ts
    import { createClient } from "@supabase/supabase-js";
    import AsyncStorage from "@react-native-async-storage/async-storage";
    import * as SecureStore from 'expo-secure-store';
    import * as aesjs from 'aes-js';
    import 'react-native-get-random-values';

    // As Expo's SecureStore does not support values larger than 2048
    // bytes, an AES-256 key is generated and stored in SecureStore, while
    // it is used to encrypt/decrypt values stored in AsyncStorage.
    class LargeSecureStore {
      private async _encrypt(key: string, value: string) {
        const encryptionKey = crypto.getRandomValues(new Uint8Array(256 / 8));

        const cipher = new aesjs.ModeOfOperation.ctr(encryptionKey, new aesjs.Counter(1));
        const encryptedBytes = cipher.encrypt(aesjs.utils.utf8.toBytes(value));

        await SecureStore.setItemAsync(key, aesjs.utils.hex.fromBytes(encryptionKey));

        return aesjs.utils.hex.fromBytes(encryptedBytes);
      }

      private async _decrypt(key: string, value: string) {
        const encryptionKeyHex = await SecureStore.getItemAsync(key);
        if (!encryptionKeyHex) {
          return encryptionKeyHex;
        }

        const cipher = new aesjs.ModeOfOperation.ctr(aesjs.utils.hex.toBytes(encryptionKeyHex), new aesjs.Counter(1));
        const decryptedBytes = cipher.decrypt(aesjs.utils.hex.toBytes(value));

        return aesjs.utils.utf8.fromBytes(decryptedBytes);
      }

      async getItem(key: string) {
        const encrypted = await AsyncStorage.getItem(key);
        if (!encrypted) { return encrypted; }

        return await this._decrypt(key, encrypted);
      }

      async removeItem(key: string) {
        await AsyncStorage.removeItem(key);
        await SecureStore.deleteItemAsync(key);
      }

      async setItem(key: string, value: string) {
        const encrypted = await this._encrypt(key, value);

        await AsyncStorage.setItem(key, encrypted);
      }
    }

    const supabaseUrl = YOUR_REACT_NATIVE_SUPABASE_URL
    const supabasePublishableKey = YOUR_REACT_NATIVE_SUPABASE_PUBLISHABLE_KEY

    const supabase = createClient(supabaseUrl, supabasePublishableKey, {
      auth: {
        storage: new LargeSecureStore(),
        autoRefreshToken: true,
        persistSession: true,
        detectSessionInUrl: false,
      },
    });
    ```

    </$CodeTabs>

  </TabPanel>
</Tabs>

### Set up a login component

Let's set up a React Native component to manage logins and sign ups.
Users would be able to sign in with their email and password.

<$CodeTabs>

```tsx name=components/Auth.tsx
import React, { useState } from 'react'
import { Alert, StyleSheet, View, AppState } from 'react-native'
import { supabase } from '../lib/supabase'
import { Button, Input } from '@rneui/themed'

// Tells Supabase Auth to continuously refresh the session automatically if
// the app is in the foreground. When this is added, you will continue to receive
// `onAuthStateChange` events with the `TOKEN_REFRESHED` or `SIGNED_OUT` event
// if the user's session is terminated. This should only be registered once.
AppState.addEventListener('change', (state) => {
  if (state === 'active') {
    supabase.auth.startAutoRefresh()
  } else {
    supabase.auth.stopAutoRefresh()
  }
})

export default function Auth() {
  const [email, setEmail] = useState('')
  const [password, setPassword] = useState('')
  const [loading, setLoading] = useState(false)

  async function signInWithEmail() {
    setLoading(true)
    const { error } = await supabase.auth.signInWithPassword({
      email: email,
      password: password,
    })

    if (error) Alert.alert(error.message)
    setLoading(false)
  }

  async function signUpWithEmail() {
    setLoading(true)
    const {
      data: { session },
      error,
    } = await supabase.auth.signUp({
      email: email,
      password: password,
    })

    if (error) Alert.alert(error.message)
    if (!session) Alert.alert('Please check your inbox for email verification!')
    setLoading(false)
  }

  return (
    <View style={styles.container}>
      <View style={[styles.verticallySpaced, styles.mt20]}>
        <Input
          label="Email"
          leftIcon={{ type: 'font-awesome', name: 'envelope' }}
          onChangeText={(text) => setEmail(text)}
          value={email}
          placeholder="email@address.com"
          autoCapitalize={'none'}
        />
      </View>
      <View style={styles.verticallySpaced}>
        <Input
          label="Password"
          leftIcon={{ type: 'font-awesome', name: 'lock' }}
          onChangeText={(text) => setPassword(text)}
          value={password}
          secureTextEntry={true}
          placeholder="Password"
          autoCapitalize={'none'}
        />
      </View>
      <View style={[styles.verticallySpaced, styles.mt20]}>
        <Button title="Sign in" disabled={loading} onPress={() => signInWithEmail()} />
      </View>
      <View style={styles.verticallySpaced}>
        <Button title="Sign up" disabled={loading} onPress={() => signUpWithEmail()} />
      </View>
    </View>
  )
}

const styles = StyleSheet.create({
  container: {
    marginTop: 40,
    padding: 12,
  },
  verticallySpaced: {
    paddingTop: 4,
    paddingBottom: 4,
    alignSelf: 'stretch',
  },
  mt20: {
    marginTop: 20,
  },
})
```

</$CodeTabs>

<Admonition type="note">

By default Supabase Auth requires email verification before a session is created for the users. To support email verification you need to [implement deep link handling](/docs/guides/auth/native-mobile-deep-linking?platform=react-native)!

While testing, you can disable email confirmation in your [project's email auth provider settings](/dashboard/project/_/auth/providers).

</Admonition>

### Account page

After a user is signed in we can allow them to edit their profile details and manage their account.

Let's create a new component for that called `Account.tsx`.

<$CodeTabs>

```tsx name=components/Account.tsx
import { useState, useEffect } from 'react'
import { supabase } from '../lib/supabase'
import { StyleSheet, View, Alert } from 'react-native'
import { Button, Input } from '@rneui/themed'
import { Session } from '@supabase/supabase-js'

export default function Account({ session }: { session: Session }) {
  const [loading, setLoading] = useState(true)
  const [username, setUsername] = useState('')
  const [website, setWebsite] = useState('')
  const [avatarUrl, setAvatarUrl] = useState('')

  useEffect(() => {
    if (session) getProfile()
  }, [session])

  async function getProfile() {
    try {
      setLoading(true)
      if (!session?.user) throw new Error('No user on the session!')

      const { data, error, status } = await supabase
        .from('profiles')
        .select(`username, website, avatar_url`)
        .eq('id', session?.user.id)
        .single()
      if (error && status !== 406) {
        throw error
      }

      if (data) {
        setUsername(data.username)
        setWebsite(data.website)
        setAvatarUrl(data.avatar_url)
      }
    } catch (error) {
      if (error instanceof Error) {
        Alert.alert(error.message)
      }
    } finally {
      setLoading(false)
    }
  }

  async function updateProfile({
    username,
    website,
    avatar_url,
  }: {
    username: string
    website: string
    avatar_url: string
  }) {
    try {
      setLoading(true)
      if (!session?.user) throw new Error('No user on the session!')

      const updates = {
        id: session?.user.id,
        username,
        website,
        avatar_url,
        updated_at: new Date(),
      }

      const { error } = await supabase.from('profiles').upsert(updates)

      if (error) {
        throw error
      }
    } catch (error) {
      if (error instanceof Error) {
        Alert.alert(error.message)
      }
    } finally {
      setLoading(false)
    }
  }

  return (
    <View style={styles.container}>
      <View style={[styles.verticallySpaced, styles.mt20]}>
        <Input label="Email" value={session?.user?.email} disabled />
      </View>
      <View style={styles.verticallySpaced}>
        <Input label="Username" value={username || ''} onChangeText={(text) => setUsername(text)} />
      </View>
      <View style={styles.verticallySpaced}>
        <Input label="Website" value={website || ''} onChangeText={(text) => setWebsite(text)} />
      </View>

      <View style={[styles.verticallySpaced, styles.mt20]}>
        <Button
          title={loading ? 'Loading ...' : 'Update'}
          onPress={() => updateProfile({ username, website, avatar_url: avatarUrl })}
          disabled={loading}
        />
      </View>

      <View style={styles.verticallySpaced}>
        <Button title="Sign Out" onPress={() => supabase.auth.signOut()} />
      </View>
    </View>
  )
}

const styles = StyleSheet.create({
  container: {
    marginTop: 40,
    padding: 12,
  },
  verticallySpaced: {
    paddingTop: 4,
    paddingBottom: 4,
    alignSelf: 'stretch',
  },
  mt20: {
    marginTop: 20,
  },
})
```

</$CodeTabs>

### Launch!

Now that we have all the components in place, let's update `App.tsx`:

<$CodeTabs>

```tsx name=App.tsx
import { useState, useEffect } from 'react'
import { supabase } from './lib/supabase'
import Auth from './components/Auth'
import Account from './components/Account'
import { View } from 'react-native'
import { Session } from '@supabase/supabase-js'

export default function App() {
  const [session, setSession] = useState<Session | null>(null)

  useEffect(() => {
    supabase.auth.getSession().then(({ data: { session } }) => {
      setSession(session)
    })

    supabase.auth.onAuthStateChange((_event, session) => {
      setSession(session)
    })
  }, [])

  return (
    <View>
      {session && session.user ? <Account key={session.user.id} session={session} /> : <Auth />}
    </View>
  )
}
```

</$CodeTabs>

Once that's done, run this in a terminal window:

```bash
npm start
```

And then press the appropriate key for the environment you want to test the app in and you should see the completed app.

## Bonus: Profile photos

Every Supabase project is configured with [Storage](/docs/guides/storage) for managing large files like
photos and videos.

### Additional dependency installation

You will need an image picker that works on the environment you will build the project for, we will use `expo-image-picker` in this example.

```bash
npx expo install expo-image-picker
```

### Create an upload widget

Let's create an avatar for the user so that they can upload a profile photo.
We can start by creating a new component:

<$CodeTabs>

```tsx name=components/Avatar.tsx
import { useState, useEffect } from 'react'
import { supabase } from '../lib/supabase'
import { StyleSheet, View, Alert, Image, Button } from 'react-native'
import * as ImagePicker from 'expo-image-picker'

interface Props {
  size: number
  url: string | null
  onUpload: (filePath: string) => void
}

export default function Avatar({ url, size = 150, onUpload }: Props) {
  const [uploading, setUploading] = useState(false)
  const [avatarUrl, setAvatarUrl] = useState<string | null>(null)
  const avatarSize = { height: size, width: size }

  useEffect(() => {
    if (url) downloadImage(url)
  }, [url])

  async function downloadImage(path: string) {
    try {
      const { data, error } = await supabase.storage.from('avatars').download(path)

      if (error) {
        throw error
      }

      const fr = new FileReader()
      fr.readAsDataURL(data)
      fr.onload = () => {
        setAvatarUrl(fr.result as string)
      }
    } catch (error) {
      if (error instanceof Error) {
        console.log('Error downloading image: ', error.message)
      }
    }
  }

  async function uploadAvatar() {
    try {
      setUploading(true)

      const result = await ImagePicker.launchImageLibraryAsync({
        mediaTypes: ImagePicker.MediaTypeOptions.Images, // Restrict to only images
        allowsMultipleSelection: false, // Can only select one image
        allowsEditing: true, // Allows the user to crop / rotate their photo before uploading it
        quality: 1,
        exif: false, // We don't want nor need that data.
      })

      if (result.canceled || !result.assets || result.assets.length === 0) {
        console.log('User cancelled image picker.')
        return
      }

      const image = result.assets[0]
      console.log('Got image', image)

      if (!image.uri) {
        throw new Error('No image uri!') // Realistically, this should never happen, but just in case...
      }

      const arraybuffer = await fetch(image.uri).then((res) => res.arrayBuffer())

      const fileExt = image.uri?.split('.').pop()?.toLowerCase() ?? 'jpeg'
      const path = `${Date.now()}.${fileExt}`
      const { data, error: uploadError } = await supabase.storage
        .from('avatars')
        .upload(path, arraybuffer, {
          contentType: image.mimeType ?? 'image/jpeg',
        })

      if (uploadError) {
        throw uploadError
      }

      onUpload(data.path)
    } catch (error) {
      if (error instanceof Error) {
        Alert.alert(error.message)
      } else {
        throw error
      }
    } finally {
      setUploading(false)
    }
  }

  return (
    <View>
      {avatarUrl ? (
        <Image
          source={{ uri: avatarUrl }}
          accessibilityLabel="Avatar"
          style={[avatarSize, styles.avatar, styles.image]}
        />
      ) : (
        <View style={[avatarSize, styles.avatar, styles.noImage]} />
      )}
      <View>
        <Button
          title={uploading ? 'Uploading ...' : 'Upload'}
          onPress={uploadAvatar}
          disabled={uploading}
        />
      </View>
    </View>
  )
}

const styles = StyleSheet.create({
  avatar: {
    borderRadius: 5,
    overflow: 'hidden',
    maxWidth: '100%',
  },
  image: {
    objectFit: 'cover',
    paddingTop: 0,
  },
  noImage: {
    backgroundColor: '#333',
    borderWidth: 1,
    borderStyle: 'solid',
    borderColor: 'rgb(200, 200, 200)',
    borderRadius: 5,
  },
})
```

</$CodeTabs>

### Add the new widget

And then we can add the widget to the Account page:

<$CodeTabs>

```tsx name=components/Account.tsx
// Import the new component
import Avatar from './Avatar'

// ...
return (
  <View>
    {/* Add to the body */}
    <View>
      <Avatar
        size={200}
        url={avatarUrl}
        onUpload={(url: string) => {
          setAvatarUrl(url)
          updateProfile({ username, website, avatar_url: url })
        }}
      />
    </View>
    {/* ... */}
  </View>
)
// ...
```

</$CodeTabs>

Now you will need to run the prebuild command to get the application working on your chosen platform.

```bash
npx expo prebuild
```

At this stage you have a fully functional application!


## Links discovered
- [Supabase User Management example](https://github.com/supabase/supabase/blob/master/docs/img/supabase-flutter-demo.png)
- [full example on GitHub](https://github.com/supabase/supabase/tree/master/examples/user-management/expo-user-management)
- [`expo`](https://docs.expo.dev/get-started/create-a-new-app/)
- [supabase-js](https://github.com/supabase/supabase-js)
- [Row Level Security](https://github.com/supabase/supabase/blob/master/docs/guides/database/postgres/row-level-security.md)
- [Expo SecureStore](https://docs.expo.dev/versions/latest/sdk/securestore)
- [`aes-js` library](https://github.com/ricmoo/aes-js)
- [`SecureStoreOptions`](https://docs.expo.dev/versions/latest/sdk/securestore/#securestoreoptions)
- [`SecureStore.WHEN_UNLOCKED`](https://docs.expo.dev/versions/latest/sdk/securestore/#securestorewhen_unlocked)
- [implement deep link handling](https://github.com/supabase/supabase/blob/master/docs/guides/auth/native-mobile-deep-linking?platform=react-native.md)
- [project's email auth provider settings](https://github.com/supabase/supabase/blob/master/dashboard/project/_/auth/providers.md)
- [Storage](https://github.com/supabase/supabase/blob/master/docs/guides/storage.md)

--- apps/docs/content/guides/getting-started/tutorials/with-flutter.mdx ---
---
title: 'Build a User Management App with Flutter'
description: 'Learn how to use Supabase in your Flutter App.'
tocVideo: 'r7ysVtZ5Row'
---

<$Partial path="quickstart_intro.mdx" />

![Supabase User Management example](/docs/img/supabase-flutter-demo.png)

<Admonition type="note">

If you get stuck while working through this guide, refer to the [full example on GitHub](https://github.com/supabase/supabase/tree/master/examples/user-management/flutter-user-management).

</Admonition>

<$Partial path="project_setup.mdx" variables={{ "framework": "flutter", "tab": "mobiles" }} />

## Building the app

Let's start building the Flutter app from scratch.

### Initialize a Flutter app

We can use [`flutter create`](https://flutter.dev/docs/get-started/test-drive) to initialize
an app called `supabase_quickstart`:

```bash
flutter create supabase_quickstart
```

Then let's install the only additional dependency: [`supabase_flutter`](https://pub.dev/packages/supabase_flutter)

Copy and paste the following line in your pubspec.yaml to install the package:

```yaml
supabase_flutter: ^2.0.0
```

Run `flutter pub get` to install the dependencies.

### Setup deep links

Now that we have the dependencies installed let's setup deep links.
Setting up deep links is required to bring back the user to the app when they click on the magic link to sign in.
We can setup deep links with just a minor tweak on our Flutter application.

We have to use `io.supabase.flutterquickstart` as the scheme. In this example, we will use `login-callback` as the host for our deep link, but you can change it to whatever you would like.

First, add `io.supabase.flutterquickstart://login-callback/` as a new [redirect URL](/dashboard/project/_/auth/url-configuration) in the Dashboard.

![Supabase console deep link setting](/docs/img/deeplink-setting.png)

That is it on Supabase's end and the rest are platform specific settings:

<Tabs
  scrollable
  size="small"
  type="underlined"
  defaultActiveId="ios"
  queryGroup="platform"
>
<TabPanel id="ios" label="iOS">
Edit the `ios/Runner/Info.plist` file.

Add `CFBundleURLTypes` to enable deep linking:

<$CodeTabs>

```xml name=ios/Runner/Info.plist"
<!-- ... other tags -->
<plist>
<dict>
  <!-- ... other tags -->

  <!-- Add this array for Deep Links -->
  <key>CFBundleURLTypes</key>
  <array>
    <dict>
      <key>CFBundleTypeRole</key>
      <string>Editor</string>
      <key>CFBundleURLSchemes</key>
      <array>
        <string>io.supabase.flutterquickstart</string>
      </array>
    </dict>
  </array>
  <!-- ... other tags -->
</dict>
</plist>
```

</$CodeTabs>

</TabPanel>
<TabPanel id="android" label="Android">
Edit the `android/app/src/main/AndroidManifest.xml` file.

Add an intent-filter to enable deep linking:

<$CodeTabs>

```xml name=android/app/src/main/AndroidManifest.xml
<manifest ...>
  <!-- ... other tags -->
  <application ...>
    <activity ...>
      <!-- ... other tags -->

      <!-- Add this intent-filter for Deep Links -->
      <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <!-- Accepts URIs that begin with YOUR_SCHEME://YOUR_HOST -->
        <data
          android:scheme="io.supabase.flutterquickstart"
          android:host="login-callback" />
      </intent-filter>

    </activity>
  </application>
</manifest>
```

</$CodeTabs>

</TabPanel>
<TabPanel id="web" label="Web">
Supabase redirects do not work with Flutter's [default URL strategy](https://docs.flutter.dev/ui/navigation/url-strategies).
We can switch to the path URL strategy as follows:

```dart
import 'package:flutter_web_plugins/url_strategy.dart';

void main() {
  usePathUrlStrategy();
  runApp(ExampleApp());
}
```

</TabPanel>
</Tabs>

### Main function

Now that we have deep links ready let's initialize the Supabase client inside our `main` function with the API credentials that you copied [earlier](#get-the-api-keys). These variables will be exposed on the app, and that's completely fine since we have [Row Level Security](/docs/guides/auth#row-level-security) enabled on our Database.

<$CodeTabs>

```dart name=lib/main.dart
import 'package:flutter/material.dart';
import 'package:supabase_flutter/supabase_flutter.dart';

Future<void> main() async {
  await Supabase.initialize(
    url: 'YOUR_SUPABASE_URL',
    anonKey: 'YOUR_SUPABASE_PUBLISHABLE_KEY',
  );
  runApp(const MyApp());
}

final supabase = Supabase.instance.client;

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(title: 'Supabase Flutter');
  }
}

extension ContextExtension on BuildContext {
  void showSnackBar(String message, {bool isError = false}) {
    ScaffoldMessenger.of(this).showSnackBar(
      SnackBar(
        content: Text(message),
        backgroundColor: isError
            ? Theme.of(this).colorScheme.error
            : Theme.of(this).snackBarTheme.backgroundColor,
      ),
    );
  }
}
```

</$CodeTabs>

Notice that we have a `showSnackBar` extension method that we will use to show snack bars in the app. You could define this method in a separate file and import it where needed, but for simplicity, we will define it here.

### Set up a login page

Let's create a Flutter widget to manage logins and sign ups. We will use Magic Links, so users can sign in with their email without using passwords.

Notice that this page sets up a listener on the user's auth state using `onAuthStateChange`. A new event will fire when the user comes back to the app by clicking their magic link, which this page can catch and redirect the user accordingly.

<$CodeTabs>

```dart name=lib/pages/login_page.dart
import 'dart:async';

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
import 'package:supabase_quickstart/main.dart';
import 'package:supabase_quickstart/pages/account_page.dart';

class LoginPage extends StatefulWidget {
  const LoginPage({super.key});

  @override
  State<LoginPage> createState() => _LoginPageState();
}

class _LoginPageState extends State<LoginPage> {
  bool _isLoading = false;
  bool _redirecting = false;
  late final TextEditingController _emailController = TextEditingController();
  late final StreamSubscription<AuthState> _authStateSubscription;

  Future<void> _signIn() async {
    try {
      setState(() {
        _isLoading = true;
      });
      await supabase.auth.signInWithOtp(
        email: _emailController.text.trim(),
        emailRedirectTo:
            kIsWeb ? null : 'io.supabase.flutterquickstart://login-callback/',
      );
      if (mounted) {
        context.showSnackBar('Check your email for a login link!');

        _emailController.clear();
      }
    } on AuthException catch (error) {
      if (mounted) context.showSnackBar(error.message, isError: true);
    } catch (error) {
      if (mounted) {
        context.showSnackBar('Unexpected error occurred', isError: true);
      }
    } finally {
      if (mounted) {
        setState(() {
          _isLoading = false;
        });
      }
    }
  }

  @override
  void initState() {
    _authStateSubscription = supabase.auth.onAuthStateChange.listen(
      (data) {
        if (_redirecting) return;
        final session = data.session;
        if (session != null) {
          _redirecting = true;
          Navigator.of(context).pushReplacement(
            MaterialPageRoute(builder: (context) => const AccountPage()),
          );
        }
      },
      onError: (error) {
        if (error is AuthException) {
          context.showSnackBar(error.message, isError: true);
        } else {
          context.showSnackBar('Unexpected error occurred', isError: true);
        }
      },
    );
    super.initState();
  }

  @override
  void dispose() {
    _emailController.dispose();
    _authStateSubscription.cancel();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Sign In')),
      body: ListView(
        padding: const EdgeInsets.symmetric(vertical: 18, horizontal: 12),
        children: [
          const Text('Sign in via the magic link with your email below'),
          const SizedBox(height: 18),
          TextFormField(
            controller: _emailController,
            decoration: const InputDecoration(labelText: 'Email'),
          ),
          const SizedBox(height: 18),
          ElevatedButton(
            onPressed: _isLoading ? null : _signIn,
            child: Text(_isLoading ? 'Sending...' : 'Send Magic Link'),
          ),
        ],
      ),
    );
  }
}
```

</$CodeTabs>

### Set up account page

After a user is signed in we can allow them to edit their profile details and manage their account.
Let's create a new widget called `account_page.dart` for that.

<$CodeTabs>

```dart name=lib/pages/account_page.dart"
import 'package:flutter/material.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
import 'package:supabase_quickstart/main.dart';
import 'package:supabase_quickstart/pages/login_page.dart';

class AccountPage extends StatefulWidget {
  const AccountPage({super.key});

  @override
  State<AccountPage> createState() => _AccountPageState();
}

class _AccountPageState extends State<AccountPage> {
  final _usernameController = TextEditingController();
  final _websiteController = TextEditingController();

  String? _avatarUrl;
  var _loading = true;

  /// Called once a user id is received within `onAuthenticated()`
  Future<void> _getProfile() async {
    setState(() {
      _loading = true;
    });

    try {
      final userId = supabase.auth.currentSession!.user.id;
      final data =
          await supabase.from('profiles').select().eq('id', userId).single();
      _usernameController.text = (data['username'] ?? '') as String;
      _websiteController.text = (data['website'] ?? '') as String;
      _avatarUrl = (data['avatar_url'] ?? '') as String;
    } on PostgrestException catch (error) {
      if (mounted) context.showSnackBar(error.message, isError: true);
    } catch (error) {
      if (mounted) {
        context.showSnackBar('Unexpected error occurred', isError: true);
      }
    } finally {
      if (mounted) {
        setState(() {
          _loading = false;
        });
      }
    }
  }

  /// Called when user taps `Update` button
  Future<void> _updateProfile() async {
    setState(() {
      _loading = true;
    });
    final userName = _usernameController.text.trim();
    final website = _websiteController.text.trim();
    final user = supabase.auth.currentUser;
    final updates = {
      'id': user!.id,
      'username': userName,
      'website': website,
      'updated_at': DateTime.now().toIso8601String(),
    };
    try {
      await supabase.from('profiles').upsert(updates);
      if (mounted) context.showSnackBar('Successfully updated profile!');
    } on PostgrestException catch (error) {
      if (mounted) context.showSnackBar(error.message, isError: true);
    } catch (error) {
      if (mounted) {
        context.showSnackBar('Unexpected error occurred', isError: true);
      }
    } finally {
      if (mounted) {
        setState(() {
          _loading = false;
        });
      }
    }
  }

  Future<void> _signOut() async {
    try {
      await supabase.auth.signOut();
    } on AuthException catch (error) {
      if (mounted) context.showSnackBar(error.message, isError: true);
    } catch (error) {
      if (mounted) {
        context.showSnackBar('Unexpected error occurred', isError: true);
      }
    } finally {
      if (mounted) {
        Navigator.of(context).pushReplacement(
          MaterialPageRoute(builder: (_) => const LoginPage()),
        );
      }
    }
  }

  @override
  void initState() {
    super.initState();
    _getProfile();
  }

  @override
  void dispose() {
    _usernameController.dispose();
    _websiteController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Profile')),
      body: ListView(
        padding: const EdgeInsets.symmetric(vertical: 18, horizontal: 12),
        children: [
          TextFormField(
            controller: _usernameController,
            decoration: const InputDecoration(labelText: 'User Name'),
          ),
          const SizedBox(height: 18),
          TextFormField(
            controller: _websiteController,
            decoration: const InputDecoration(labelText: 'Website'),
          ),
          const SizedBox(height: 18),
          ElevatedButton(
            onPressed: _loading ? null : _updateProfile,
            child: Text(_loading ? 'Saving...' : 'Update'),
          ),
          const SizedBox(height: 18),
          TextButton(onPressed: _signOut, child: const Text('Sign Out')),
        ],
      ),
    );
  }
}
```

</$CodeTabs>

### Launch!

Now that we have all the components in place, let's update `lib/main.dart`.
The `home` of the `MaterialApp`, meaning the initial page shown to the user, will be the `LoginPage` if the user is not authenticated, and the `AccountPage` if the user is authenticated.
We also included some theming to make the app look a bit nicer.

<$CodeTabs>

```dart name=lib/main.dart
import 'package:flutter/material.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
import 'package:supabase_quickstart/pages/account_page.dart';
import 'package:supabase_quickstart/pages/login_page.dart';

Future<void> main() async {
  await Supabase.initialize(
    url: 'YOUR_SUPABASE_URL',
    anonKey: 'YOUR_SUPABASE_PUBLISHABLE_KEY',
  );
  runApp(const MyApp());
}

final supabase = Supabase.instance.client;

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Supabase Flutter',
      theme: ThemeData.dark().copyWith(
        primaryColor: Colors.green,
        textButtonTheme: TextButtonThemeData(
          style: TextButton.styleFrom(
            foregroundColor: Colors.green,
          ),
        ),
        elevatedButtonTheme: ElevatedButtonThemeData(
          style: ElevatedButton.styleFrom(
            foregroundColor: Colors.white,
            backgroundColor: Colors.green,
          ),
        ),
      ),
      home: supabase.auth.currentSession == null
          ? const LoginPage()
          : const AccountPage(),
    );
  }
}

extension ContextExtension on BuildContext {
  void showSnackBar(String message, {bool isError = false}) {
    ScaffoldMessenger.of(this).showSnackBar(
      SnackBar(
        content: Text(message),
        backgroundColor: isError
            ? Theme.of(this).colorScheme.error
            : Theme.of(this).snackBarTheme.backgroundColor,
      ),
    );
  }
}
```

</$CodeTabs>

Once that's done, run this in a terminal window to launch on Android or iOS:

```bash
flutter run
```

Or for web, run the following command to launch it on `localhost:3000`

```bash
flutter run -d web-server --web-hostname localhost --web-port 3000
```

And then open the browser to [localhost:3000](http://localhost:3000) and you should see the completed app.

![Supabase User Management example](/docs/img/supabase-flutter-account-page.png)

## Bonus: Profile photos

Every Supabase project is configured with [Storage](/docs/guides/storage) for managing large files like
photos and videos.

### Making sure we have a public bucket

We will be storing the image as a publicly sharable image.
Make sure your `avatars` bucket is set to public, and if it is not, change the publicity by clicking the dot menu that appears when you hover over the bucket name.
You should see an orange `Public` badge next to your bucket name if your bucket is set to public.

### Adding image uploading feature to account page

We will use [`image_picker`](https://pub.dev/packages/image_picker) plugin to select an image from the device.

Add the following line in your pubspec.yaml file to install `image_picker`:

```yaml
image_picker: ^1.0.5
```

Using [`image_picker`](https://pub.dev/packages/image_picker) requires some additional preparation depending on the platform.
Follow the instruction on README.md of [`image_picker`](https://pub.dev/packages/image_picker) on how to set it up for the platform you are using.

Once you are done with all of the above, it is time to dive into coding.

### Create an upload widget

Let's create an avatar for the user so that they can upload a profile photo.
We can start by creating a new component:

<$CodeTabs>

```dart name=lib/components/avatar.dart
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
import 'package:supabase_quickstart/main.dart';

class Avatar extends StatefulWidget {
  const Avatar({
    super.key,
    required this.imageUrl,
    required this.onUpload,
  });

  final String? imageUrl;
  final void Function(String) onUpload;

  @override
  State<Avatar> createState() => _AvatarState();
}

class _AvatarState extends State<Avatar> {
  bool _isLoading = false;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        if (widget.imageUrl == null || widget.imageUrl!.isEmpty)
          Container(
            width: 150,
            height: 150,
            color: Colors.grey,
            child: const Center(
              child: Text('No Image'),
            ),
          )
        else
          Image.network(
            widget.imageUrl!,
            width: 150,
            height: 150,
            fit: BoxFit.cover,
          ),
        ElevatedButton(
          onPressed: _isLoading ? null : _upload,
          child: const Text('Upload'),
        ),
      ],
    );
  }

  Future<void> _upload() async {
    final picker = ImagePicker();
    final imageFile = await picker.pickImage(
      source: ImageSource.gallery,
      maxWidth: 300,
      maxHeight: 300,
    );
    if (imageFile == null) {
      return;
    }
    setState(() => _isLoading = true);

    try {
      final bytes = await imageFile.readAsBytes();
      final fileExt = imageFile.path.split('.').last;
      final fileName = '${DateTime.now().toIso8601String()}.$fileExt';
      final filePath = fileName;
      await supabase.storage.from('avatars').uploadBinary(
            filePath,
            bytes,
            fileOptions: FileOptions(contentType: imageFile.mimeType),
          );
      final imageUrlResponse = await supabase.storage
          .from('avatars')
          .createSignedUrl(filePath, 60 * 60 * 24 * 365 * 10);
      widget.onUpload(imageUrlResponse);
    } on StorageException catch (error) {
      if (mounted) {
        context.showSnackBar(error.message, isError: true);
      }
    } catch (error) {
      if (mounted) {
        context.showSnackBar('Unexpected error occurred', isError: true);
      }
    }

    setState(() => _isLoading = false);
  }
}
```

</$CodeTabs>

### Add the new widget

And then we can add the widget to the Account page as well as some logic to update the `avatar_url` whenever the user uploads a new avatar.

<$CodeTabs>

```dart name=lib/pages/account_page.dart
import 'package:flutter/material.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
import 'package:supabase_quickstart/components/avatar.dart';
import 'package:supabase_quickstart/main.dart';
import 'package:supabase_quickstart/pages/login_page.dart';

class AccountPage extends StatefulWidget {
  const AccountPage({super.key});

  @override
  State<AccountPage> createState() => _AccountPageState();
}

class _AccountPageState extends State<AccountPage> {
  final _usernameController = TextEditingController();
  final _websiteController = TextEditingController();

  String? _avatarUrl;
  var _loading = true;

  /// Called once a user id is received within `onAuthenticated()`
  Future<void> _getProfile() async {
    setState(() {
      _loading = true;
    });

    try {
      final userId = supabase.auth.currentSession!.user.id;
      final data =
          await supabase.from('profiles').select().eq('id', userId).single();
      _usernameController.text = (data['username'] ?? '') as String;
      _websiteController.text = (data['website'] ?? '') as String;
      _avatarUrl = (data['avatar_url'] ?? '') as String;
    } on PostgrestException catch (error) {
      if (mounted) context.showSnackBar(error.message, isError: true);
    } catch (error) {
      if (mounted) {
        context.showSnackBar('Unexpected error occurred', isError: true);
      }
    } finally {
      if (mounted) {
        setState(() {
          _loading = false;
        });
      }
    }
  }

  /// Called when user taps `Update` button
  Future<void> _updateProfile() async {
    setState(() {
      _loading = true;
    });
    final userName = _usernameController.text.trim();
    final website = _websiteController.text.trim();
    final user = supabase.auth.currentUser;
    final updates = {
      'id': user!.id,
      'username': userName,
      'website': website,
      'updated_at': DateTime.now().toIso8601String(),
    };
    try {
      await supabase.from('profiles').upsert(updates);
      if (mounted) context.showSnackBar('Successfully updated profile!');
    } on PostgrestException catch (error) {
      if (mounted) context.showSnackBar(error.message, isError: true);
    } catch (error) {
      if (mounted) {
        context.showSnackBar('Unexpected error occurred', isError: true);
      }
    } finally {
      if (mounted) {
        setState(() {
          _loading = false;
        });
      }
    }
  }

  Future<void> _signOut() async {
    try {
      await supabase.auth.signOut();
    } on AuthException catch (error) {
      if (mounted) context.showSnackBar(error.message, isError: true);
    } catch (error) {
      if (mounted) {
        context.showSnackBar('Unexpected error occurred', isError: true);
      }
    } finally {
      if (mounted) {
        Navigator.of(context).pushReplacement(
          MaterialPageRoute(builder: (_) => const LoginPage()),
        );
      }
    }
  }

  /// Called when image has been uploaded to Supabase storage from within Avatar widget
  Future<void> _onUpload(String imageUrl) async {
    try {
      final userId = supabase.auth.currentUser!.id;
      await supabase.from('profiles').upsert({
        'id': userId,
        'avatar_url': imageUrl,
      });
      if (mounted) {
        const SnackBar(
          content: Text('Updated your profile image!'),
        );
      }
    } on PostgrestException catch (error) {
      if (mounted) context.showSnackBar(error.message, isError: true);
    } catch (error) {
      if (mounted) {
        context.showSnackBar('Unexpected error occurred', isError: true);
      }
    }
    if (!mounted) {
      return;
    }

    setState(() {
      _avatarUrl = imageUrl;
    });
  }

  @override
  void initState() {
    super.initState();
    _getProfile();
  }

  @override
  void dispose() {
    _usernameController.dispose();
    _websiteController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Profile')),
      body: ListView(
        padding: const EdgeInsets.symmetric(vertical: 18, horizontal: 12),
        children: [
          Avatar(
            imageUrl: _avatarUrl,
            onUpload: _onUpload,
          ),
          const SizedBox(height: 18),
          TextFormField(
            controller: _usernameController,
            decoration: const InputDecoration(labelText: 'User Name'),
          ),
          const SizedBox(height: 18),
          TextFormField(
            controller: _websiteController,
            decoration: const InputDecoration(labelText: 'Website'),
          ),
          const SizedBox(height: 18),
          ElevatedButton(
            onPressed: _loading ? null : _updateProfile,
            child: Text(_loading ? 'Saving...' : 'Update'),
          ),
          const SizedBox(height: 18),
          TextButton(onPressed: _signOut, child: const Text('Sign Out')),
        ],
      ),
    );
  }
}
```

</$CodeTabs>

Congratulations, you've built a fully functional user management app using Flutter and Supabase!

## See also

- [Flutter Tutorial: building a Flutter chat app](/blog/flutter-tutorial-building-a-chat-app)
- [Flutter Tutorial - Part 2: Authentication and Authorization with RLS](/blog/flutter-authentication-and-authorization-with-rls)


## Links discovered
- [Supabase User Management example](https://github.com/supabase/supabase/blob/master/docs/img/supabase-flutter-demo.png)
- [full example on GitHub](https://github.com/supabase/supabase/tree/master/examples/user-management/flutter-user-management)
- [`flutter create`](https://flutter.dev/docs/get-started/test-drive)
- [`supabase_flutter`](https://pub.dev/packages/supabase_flutter)
- [redirect URL](https://github.com/supabase/supabase/blob/master/dashboard/project/_/auth/url-configuration.md)
- [Supabase console deep link setting](https://github.com/supabase/supabase/blob/master/docs/img/deeplink-setting.png)
- [default URL strategy](https://docs.flutter.dev/ui/navigation/url-strategies)
- [Row Level Security](https://github.com/supabase/supabase/blob/master/docs/guides/auth#row-level-security.md)
- [localhost:3000](http://localhost:3000)
- [Supabase User Management example](https://github.com/supabase/supabase/blob/master/docs/img/supabase-flutter-account-page.png)
- [Storage](https://github.com/supabase/supabase/blob/master/docs/guides/storage.md)
- [`image_picker`](https://pub.dev/packages/image_picker)
- [Flutter Tutorial: building a Flutter chat app](https://github.com/supabase/supabase/blob/master/blog/flutter-tutorial-building-a-chat-app.md)
- [Flutter Tutorial - Part 2: Authentication and Authorization with RLS](https://github.com/supabase/supabase/blob/master/blog/flutter-authentication-and-authorization-with-rls.md)

--- apps/docs/content/guides/getting-started/tutorials/with-ionic-angular.mdx ---
---
title: 'Build a User Management App with Ionic Angular'
description: 'Learn how to use Supabase in your Ionic Angular App.'
---

<$Partial path="quickstart_intro.mdx" />

![Supabase User Management example](/docs/img/ionic-demos/ionic-angular-account.png)

<Admonition type="note">

If you get stuck while working through this guide, refer to the [full example on GitHub](https://github.com/mhartington/supabase-ionic-angular).

</Admonition>

<$Partial path="project_setup.mdx" variables={{ "framework": "ionicangular", "tab": "mobiles" }} />

## Building the app

Let's start building the Angular app from scratch.

### Initialize an Ionic Angular app

We can use the [Ionic CLI](https://ionicframework.com/docs/cli) to initialize
an app called `supabase-ionic-angular`:

```bash
npm install -g @ionic/cli
ionic start supabase-ionic-angular blank --type angular
cd supabase-ionic-angular
```

Then let's install the only additional dependency: [supabase-js](https://github.com/supabase/supabase-js)

```bash
npm install @supabase/supabase-js
```

And finally, we want to save the environment variables in the `src/environments/environment.ts` file.
All we need are the API URL and the key that you copied [earlier](#get-api-details).
These variables will be exposed on the browser, and that's completely fine since we have [Row Level Security](/docs/guides/auth#row-level-security) enabled on our Database.

<$CodeTabs>

```ts name=environment.ts
export const environment = {
  production: false,
  supabaseUrl: 'YOUR_SUPABASE_URL',
  supabaseKey: 'YOUR_SUPABASE_KEY',
}
```

</$CodeTabs>

Now that we have the API credentials in place, let's create a `SupabaseService` with `ionic g s supabase` to initialize the Supabase client and implement functions to communicate with the Supabase API.

<$CodeTabs>

```ts name=src/app/supabase.service.ts
import { Injectable } from '@angular/core'
import { LoadingController, ToastController } from '@ionic/angular'
import { AuthChangeEvent, createClient, Session, SupabaseClient } from '@supabase/supabase-js'
import { environment } from '../environments/environment'

export interface Profile {
  username: string
  website: string
  avatar_url: string
}

@Injectable({
  providedIn: 'root',
})
export class SupabaseService {
  private supabase: SupabaseClient

  constructor(
    private loadingCtrl: LoadingController,
    private toastCtrl: ToastController
  ) {
    this.supabase = createClient(environment.supabaseUrl, environment.supabaseKey)
  }

  get user() {
    return this.supabase.auth.getUser().then(({ data }) => data?.user)
  }

  get session() {
    return this.supabase.auth.getSession().then(({ data }) => data?.session)
  }

  get profile() {
    return this.user
      .then((user) => user?.id)
      .then((id) =>
        this.supabase.from('profiles').select(`username, website, avatar_url`).eq('id', id).single()
      )
  }

  authChanges(callback: (event: AuthChangeEvent, session: Session | null) => void) {
    return this.supabase.auth.onAuthStateChange(callback)
  }

  signIn(email: string) {
    return this.supabase.auth.signInWithOtp({ email })
  }

  signOut() {
    return this.supabase.auth.signOut()
  }

  async updateProfile(profile: Profile) {
    const user = await this.user
    const update = {
      ...profile,
      id: user?.id,
      updated_at: new Date(),
    }

    return this.supabase.from('profiles').upsert(update)
  }

  downLoadImage(path: string) {
    return this.supabase.storage.from('avatars').download(path)
  }

  uploadAvatar(filePath: string, file: File) {
    return this.supabase.storage.from('avatars').upload(filePath, file)
  }

  async createNotice(message: string) {
    const toast = await this.toastCtrl.create({ message, duration: 5000 })
    await toast.present()
  }

  createLoader() {
    return this.loadingCtrl.create()
  }
}
```

</$CodeTabs>

### Set up a login route

Let's set up a route to manage logins and signups. We'll use Magic Links so users can sign in with their email without using passwords.
Create a `LoginPage` with the `ionic g page login` Ionic CLI command.

<Admonition type="tip">

This guide will show the template inline, but the example app will have `templateUrl`s

</Admonition>

<$CodeTabs>

```ts name=src/app/login/login.page.ts
import { Component, OnInit } from '@angular/core'
import { SupabaseService } from '../supabase.service'

@Component({
  selector: 'app-login',
  template: `
    <ion-header>
      <ion-toolbar>
        <ion-title>Login</ion-title>
      </ion-toolbar>
    </ion-header>

    <ion-content>
      <div class="ion-padding">
        <h1>Supabase + Ionic Angular</h1>
        <p>Sign in via magic link with your email below</p>
      </div>
      <ion-list inset="true">
        <form (ngSubmit)="handleLogin($event)">
          <ion-item>
            <ion-label position="stacked">Email</ion-label>
            <ion-input [(ngModel)]="email" name="email" autocomplete type="email"></ion-input>
          </ion-item>
          <div class="ion-text-center">
            <ion-button type="submit" fill="clear">Login</ion-button>
          </div>
        </form>
      </ion-list>
    </ion-content>
  `,
  styleUrls: ['./login.page.scss'],
})
export class LoginPage {
  email = ''

  constructor(private readonly supabase: SupabaseService) {}

  async handleLogin(event: any) {
    event.preventDefault()
    const loader = await this.supabase.createLoader()
    await loader.present()
    try {
      const { error } = await this.supabase.signIn(this.email)
      if (error) {
        throw error
      }
      await loader.dismiss()
      await this.supabase.createNotice('Check your email for the login link!')
    } catch (error: any) {
      await loader.dismiss()
      await this.supabase.createNotice(error.error_description || error.message)
    }
  }
}
```

</$CodeTabs>

### Account page

After a user is signed in, we can allow them to edit their profile details and manage their account.
Create an `AccountComponent` with `ionic g page account` Ionic CLI command.

<$CodeTabs>

```ts name=src/app/account.page.ts
import { Component, OnInit } from '@angular/core'
import { Router } from '@angular/router'
import { Profile, SupabaseService } from '../supabase.service'

@Component({
  selector: 'app-account',
  template: `
    <ion-header>
      <ion-toolbar>
        <ion-title>Account</ion-title>
      </ion-toolbar>
    </ion-header>

    <ion-content>
      <form>
        <ion-item>
          <ion-label position="stacked">Email</ion-label>
          <ion-input type="email" name="email" [(ngModel)]="email" readonly></ion-input>
        </ion-item>

        <ion-item>
          <ion-label position="stacked">Name</ion-label>
          <ion-input type="text" name="username" [(ngModel)]="profile.username"></ion-input>
        </ion-item>

        <ion-item>
          <ion-label position="stacked">Website</ion-label>
          <ion-input type="url" name="website" [(ngModel)]="profile.website"></ion-input>
        </ion-item>
        <div class="ion-text-center">
          <ion-button fill="clear" (click)="updateProfile()">Update Profile</ion-button>
        </div>
      </form>

      <div class="ion-text-center">
        <ion-button fill="clear" (click)="signOut()">Log Out</ion-button>
      </div>
    </ion-content>
  `,
  styleUrls: ['./account.page.scss'],
})
export class AccountPage implements OnInit {
  profile: Profile = {
    username: '',
    avatar_url: '',
    website: '',
  }

  email = ''

  constructor(
    private readonly supabase: SupabaseService,
    private router: Router
  ) {}
  ngOnInit() {
    this.getEmail()
    this.getProfile()
  }

  async getEmail() {
    this.email = await this.supabase.user.then((user) => user?.email || '')
  }

  async getProfile() {
    try {
      const { data: profile, error, status } = await this.supabase.profile
      if (error && status !== 406) {
        throw error
      }
      if (profile) {
        this.profile = profile
      }
    } catch (error: any) {
      alert(error.message)
    }
  }

  async updateProfile(avatar_url: string = '') {
    const loader = await this.supabase.createLoader()
    await loader.present()
    try {
      const { error } = await this.supabase.updateProfile({ ...this.profile, avatar_url })
      if (error) {
        throw error
      }
      await loader.dismiss()
      await this.supabase.createNotice('Profile updated!')
    } catch (error: any) {
      await loader.dismiss()
      await this.supabase.createNotice(error.message)
    }
  }

  async signOut() {
    console.log('testing?')
    await this.supabase.signOut()
    this.router.navigate(['/'], { replaceUrl: true })
  }
}
```

</$CodeTabs>

### Launch!

Now that we have all the components in place, let's update `AppComponent`:

<$CodeTabs>

```ts name=src/app/app.component.ts
import { Component } from '@angular/core'
import { Router } from '@angular/router'
import { SupabaseService } from './supabase.service'

@Component({
  selector: 'app-root',
  template: `
    <ion-app>
      <ion-router-outlet></ion-router-outlet>
    </ion-app>
  `,
  styleUrls: ['app.component.scss'],
})
export class AppComponent {
  constructor(
    private supabase: SupabaseService,
    private router: Router
  ) {
    this.supabase.authChanges((_, session) => {
      console.log(session)
      if (session?.user) {
        this.router.navigate(['/account'])
      }
    })
  }
}
```

</$CodeTabs>

Then update the `AppRoutingModule`

<$CodeTabs>

```ts name=src/app/app-routing.module.ts"
import { NgModule } from '@angular/core'
import { PreloadAllModules, RouterModule, Routes } from '@angular/router'

const routes: Routes = [
  {
    path: '',
    loadChildren: () => import('./login/login.module').then((m) => m.LoginPageModule),
  },
  {
    path: 'account',
    loadChildren: () => import('./account/account.module').then((m) => m.AccountPageModule),
  },
]

@NgModule({
  imports: [
    RouterModule.forRoot(routes, {
      preloadingStrategy: PreloadAllModules,
    }),
  ],
  exports: [RouterModule],
})
export class AppRoutingModule {}
```

</$CodeTabs>

Once that's done, run this in a terminal window:

```bash
ionic serve
```

And the browser will automatically open to show the app.

![Supabase Angular](/docs/img/ionic-demos/ionic-angular.png)

## Bonus: Profile photos

Every Supabase project is configured with [Storage](/docs/guides/storage) for managing large files like photos and videos.

### Create an upload widget

Let's create an avatar for the user so that they can upload a profile photo.

First, install two packages in order to interact with the user's camera.

```bash
npm install @ionic/pwa-elements @capacitor/camera
```

[Capacitor](https://capacitorjs.com) is a cross-platform native runtime from Ionic that enables web apps to be deployed through the app store and provides access to native device API.

Ionic PWA elements is a companion package that will polyfill certain browser APIs that provide no user interface with custom Ionic UI.

With those packages installed, we can update our `main.ts` to include an additional bootstrapping call for the Ionic PWA Elements.

<$CodeTabs>

```ts name=src/main.ts
import { enableProdMode } from '@angular/core'
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'

import { AppModule } from './app/app.module'
import { environment } from './environments/environment'

import { defineCustomElements } from '@ionic/pwa-elements/loader'
defineCustomElements(window)

if (environment.production) {
  enableProdMode()
}
platformBrowserDynamic()
  .bootstrapModule(AppModule)
  .catch((err) => console.log(err))
```

</$CodeTabs>

Then create an `AvatarComponent` with this Ionic CLI command:

```bash
 ionic g component avatar --module=/src/app/account/account.module.ts --create-module
```

<$CodeTabs>

```ts name=src/app/avatar.component.ts
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'
import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser'
import { SupabaseService } from '../supabase.service'
import { Camera, CameraResultType } from '@capacitor/camera'
import { addIcons } from 'ionicons'
import { person } from 'ionicons/icons'
@Component({
  selector: 'app-avatar',
  template: `
    <div class="avatar_wrapper" (click)="uploadAvatar()">
      <img *ngIf="_avatarUrl; else noAvatar" [src]="_avatarUrl" />
      <ng-template #noAvatar>
        <ion-icon name="person" class="no-avatar"></ion-icon>
      </ng-template>
    </div>
  `,
  style: [
    `
    :host {
       display: block;
       margin: auto;
       min-height: 150px;
    }
     :host .avatar_wrapper {
       margin: 16px auto 16px;
       border-radius: 50%;
       overflow: hidden;
       height: 150px;
       aspect-ratio: 1;
       background: var(--ion-color-step-50);
       border: thick solid var(--ion-color-step-200);
    }
     :host .avatar_wrapper:hover {
       cursor: pointer;
    }
     :host .avatar_wrapper ion-icon.no-avatar {
       width: 100%;
       height: 115%;
    }
     :host img {
       display: block;
       object-fit: cover;
       width: 100%;
       height: 100%;
    }
  `,
  ],
})
export class AvatarComponent {
  _avatarUrl: SafeResourceUrl | undefined
  uploading = false

  @Input()
  set avatarUrl(url: string | undefined) {
    if (url) {
      this.downloadImage(url)
    }
  }

  @Output() upload = new EventEmitter<string>()

  constructor(
    private readonly supabase: SupabaseService,
    private readonly dom: DomSanitizer
  ) {
    addIcons({ person })
  }

  async downloadImage(path: string) {
    try {
      const { data, error } = await this.supabase.downLoadImage(path)
      if (error) {
        throw error
      }
      this._avatarUrl = this.dom.bypassSecurityTrustResourceUrl(URL.createObjectURL(data!))
    } catch (error: any) {
      console.error('Error downloading image: ', error.message)
    }
  }

  async uploadAvatar() {
    const loader = await this.supabase.createLoader()
    try {
      const photo = await Camera.getPhoto({
        resultType: CameraResultType.DataUrl,
      })

      const file = await fetch(photo.dataUrl!)
        .then((res) => res.blob())
        .then((blob) => new File([blob], 'my-file', { type: `image/${photo.format}` }))

      const fileName = `${Math.random()}-${new Date().getTime()}.${photo.format}`

      await loader.present()
      const { error } = await this.supabase.uploadAvatar(fileName, file)

      if (error) {
        throw error
      }

      this.upload.emit(fileName)
    } catch (error: any) {
      this.supabase.createNotice(error.message)
    } finally {
      loader.dismiss()
    }
  }
}
```

</$CodeTabs>

### Add the new widget

And then, we can add the widget on top of the `AccountComponent` HTML template:

<$CodeTabs>

```ts name=src/app/account.component.ts
template: `
<ion-header>
  <ion-toolbar>
    <ion-title>Account</ion-title>
  </ion-toolbar>
</ion-header>

<ion-content>
  <app-avatar
    [avatarUrl]="this.profile?.avatar_url"
    (upload)="updateProfile($event)"
  ></app-avatar>

<!-- input fields -->
`
```

</$CodeTabs>

At this stage, you have a fully functional application!

## See also

- [Authentication in Ionic Angular with Supabase](/blog/authentication-in-ionic-angular)


## Links discovered
- [Supabase User Management example](https://github.com/supabase/supabase/blob/master/docs/img/ionic-demos/ionic-angular-account.png)
- [full example on GitHub](https://github.com/mhartington/supabase-ionic-angular)
- [Ionic CLI](https://ionicframework.com/docs/cli)
- [supabase-js](https://github.com/supabase/supabase-js)
- [Row Level Security](https://github.com/supabase/supabase/blob/master/docs/guides/auth#row-level-security.md)
- [Supabase Angular](https://github.com/supabase/supabase/blob/master/docs/img/ionic-demos/ionic-angular.png)
- [Storage](https://github.com/supabase/supabase/blob/master/docs/guides/storage.md)
- [Capacitor](https://capacitorjs.com)
- [Authentication in Ionic Angular with Supabase](https://github.com/supabase/supabase/blob/master/blog/authentication-in-ionic-angular.md)

--- apps/docs/content/guides/getting-started/tutorials/with-ionic-react.mdx ---
---
title: 'Build a User Management App with Ionic React'
description: 'Learn how to use Supabase in your Ionic React App.'
---

<$Partial path="quickstart_intro.mdx" />

![Supabase User Management example](/docs/img/ionic-demos/ionic-angular-account.png)

<Admonition type="note">

If you get stuck while working through this guide, refer to the [full example on GitHub](https://github.com/mhartington/supabase-ionic-react).

</Admonition>

<$Partial path="project_setup.mdx" variables={{ "framework": "ionicreact", "tab": "mobiles" }} />

## Building the app

Let's start building the React app from scratch.

### Initialize an Ionic React app

We can use the [Ionic CLI](https://ionicframework.com/docs/cli) to initialize
an app called `supabase-ionic-react`:

```bash
npm install -g @ionic/cli
ionic start supabase-ionic-react blank --type react
cd supabase-ionic-react
```

Then let's install the only additional dependency: [supabase-js](https://github.com/supabase/supabase-js)

```bash
npm install @supabase/supabase-js
```

And finally we want to save the environment variables in a `.env`.
All we need are the API URL and the key that you copied [earlier](#get-api-details).

<$CodeTabs>

```bash name=.env
VITE_SUPABASE_URL=YOUR_SUPABASE_URL
VITE_SUPABASE_PUBLISHABLE_KEY=YOUR_SUPABASE_PUBLISHABLE_KEY
```

</$CodeTabs>

Now that we have the API credentials in place, let's create a helper file to initialize the Supabase client. These variables will be exposed
on the browser, and that's completely fine since we have [Row Level Security](/docs/guides/auth#row-level-security) enabled on our Database.

<$CodeTabs>

```js name=src/supabaseClient.ts
import { createClient } from '@supabase/supabase-js'

const supabaseUrl = import.meta.env.VITE_SUPABASE_URL || ''
const supabasePublishableKey = import.meta.env.VITE_SUPABASE_PUBLISHABLE_KEY || ''

export const supabase = createClient(supabaseUrl, supabasePublishableKey)
```

</$CodeTabs>

### Set up a login route

Let's set up a React component to manage logins and sign ups. We'll use Magic Links, so users can sign in with their email without using passwords.

<$CodeTabs>

```jsx name=/src/pages/Login.tsx
import { useState } from 'react';
import {
  IonButton,
  IonContent,
  IonHeader,
  IonInput,
  IonItem,
  IonLabel,
  IonList,
  IonPage,
  IonTitle,
  IonToolbar,
  useIonToast,
  useIonLoading,
} from '@ionic/react';

import {supabase} from '../supabaseClient'

export function LoginPage() {
  const [email, setEmail] = useState('');

  const [showLoading, hideLoading] = useIonLoading();
  const [showToast ] = useIonToast();
  const handleLogin = async (e: React.FormEvent<HTMLFormElement>) => {
    console.log()
    e.preventDefault();
    await showLoading();
    try {
      await supabase.auth.signInWithOtp({
        "email": email
      });
      await showToast({ message: 'Check your email for the login link!' });
    } catch (e: any) {
      await showToast({ message: e.error_description || e.message , duration: 5000});
    } finally {
      await hideLoading();
    }
  };
  return (
    <IonPage>
      <IonHeader>
        <IonToolbar>
          <IonTitle>Login</IonTitle>
        </IonToolbar>
      </IonHeader>

      <IonContent>
        <div className="ion-padding">
          <h1>Supabase + Ionic React</h1>
          <p>Sign in via magic link with your email below</p>
        </div>
        <IonList inset={true}>
          <form onSubmit={handleLogin}>
            <IonItem>
              <IonLabel position="stacked">Email</IonLabel>
              <IonInput
                value={email}
                name="email"
                onIonChange={(e) => setEmail(e.detail.value ?? '')}
                type="email"
              ></IonInput>
            </IonItem>
            <div className="ion-text-center">
              <IonButton type="submit" fill="clear">
                Login
              </IonButton>
            </div>
          </form>
        </IonList>
      </IonContent>
    </IonPage>
  );
}
```

</$CodeTabs>

### Account page

After a user is signed in we can allow them to edit their profile details and manage their account.

Let's create a new component for that called `Account.tsx`.

<$CodeTabs>

```jsx name=src/pages/Account.tsx
import {
  IonButton,
  IonContent,
  IonHeader,
  IonInput,
  IonItem,
  IonLabel,
  IonPage,
  IonTitle,
  IonToolbar,
  useIonLoading,
  useIonToast,
  useIonRouter
} from '@ionic/react';
import { useEffect, useState } from 'react';
import { supabase } from '../supabaseClient';
import { Session } from '@supabase/supabase-js';

export function AccountPage() {
  const [showLoading, hideLoading] = useIonLoading();
  const [showToast] = useIonToast();
  const [session, setSession] = useState<Session | null>(null)
  const router = useIonRouter();
  const [profile, setProfile] = useState({
    username: '',
    website: '',
    avatar_url: '',
  });

  useEffect(() => {
    const getSession = async () => {
      setSession(await supabase.auth.getSession().then((res) => res.data.session))
    }
    getSession()
    supabase.auth.onAuthStateChange((_event, session) => {
      setSession(session)
    })
  }, [])

  useEffect(() => {
    getProfile();
  }, [session]);
  const getProfile = async () => {
    console.log('get');
    await showLoading();
    try {
      const user = await supabase.auth.getUser();
      const { data, error, status } = await supabase
        .from('profiles')
        .select(`username, website, avatar_url`)
        .eq('id', user!.data.user?.id)
        .single();

      if (error && status !== 406) {
        throw error;
      }

      if (data) {
        setProfile({
          username: data.username,
          website: data.website,
          avatar_url: data.avatar_url,
        });
      }
    } catch (error: any) {
      showToast({ message: error.message, duration: 5000 });
    } finally {
      await hideLoading();
    }
  };
  const signOut = async () => {
    await supabase.auth.signOut();
    router.push('/', 'forward', 'replace');
  }
  const updateProfile = async (e?: any, avatar_url: string = '') => {
    e?.preventDefault();

    console.log('update ');
    await showLoading();

    try {
      const user = await supabase.auth.getUser();

      const updates = {
        id: user!.data.user?.id,
        ...profile,
        avatar_url: avatar_url,
        updated_at: new Date(),
      };

      const { error } = await supabase.from('profiles').upsert(updates);

      if (error) {
        throw error;
      }
    } catch (error: any) {
      showToast({ message: error.message, duration: 5000 });
    } finally {
      await hideLoading();
    }
  };
  return (
    <IonPage>
      <IonHeader>
        <IonToolbar>
          <IonTitle>Account</IonTitle>
        </IonToolbar>
      </IonHeader>

      <IonContent>
        <form onSubmit={updateProfile}>
          <IonItem>
            <IonLabel>
              <p>Email</p>
              <p>{session?.user?.email}</p>
            </IonLabel>
          </IonItem>

          <IonItem>
            <IonLabel position="stacked">Name</IonLabel>
            <IonInput
              type="text"
              name="username"
              value={profile.username}
              onIonChange={(e) =>
                setProfile({ ...profile, username: e.detail.value ?? '' })
              }
            ></IonInput>
          </IonItem>

          <IonItem>
            <IonLabel position="stacked">Website</IonLabel>
            <IonInput
              type="url"
              name="website"
              value={profile.website}
              onIonChange={(e) =>
                setProfile({ ...profile, website: e.detail.value ?? '' })
              }
            ></IonInput>
          </IonItem>
          <div className="ion-text-center">
            <IonButton fill="clear" type="submit">
              Update Profile
            </IonButton>
          </div>
        </form>

        <div className="ion-text-center">
          <IonButton fill="clear" onClick={signOut}>
            Log Out
          </IonButton>
        </div>
      </IonContent>
    </IonPage>
  );
}
```

</$CodeTabs>

### Launch!

Now that we have all the components in place, let's update `App.tsx`:

<$CodeTabs>

```jsx name=src/App.tsx
import { Redirect, Route } from 'react-router-dom'
import { IonApp, IonRouterOutlet, setupIonicReact } from '@ionic/react'
import { IonReactRouter } from '@ionic/react-router'
import { supabase } from './supabaseClient'

import '@ionic/react/css/ionic.bundle.css'

/* Theme variables */
import './theme/variables.css'
import { LoginPage } from './pages/Login'
import { AccountPage } from './pages/Account'
import { useEffect, useState } from 'react'
import { Session } from '@supabase/supabase-js'

setupIonicReact()

const App: React.FC = () => {
  const [session, setSession] = useState<Session | null>(null)
  useEffect(() => {
    const getSession = async () => {
      setSession(await supabase.auth.getSession().then((res) => res.data.session))
    }
    getSession()
    supabase.auth.onAuthStateChange((_event, session) => {
      setSession(session)
    })
  }, [])
  return (
    <IonApp>
      <IonReactRouter>
        <IonRouterOutlet>
          <Route
            exact
            path="/"
            render={() => {
              return session ? <Redirect to="/account" /> : <LoginPage />
            }}
          />
          <Route exact path="/account">
            <AccountPage />
          </Route>
        </IonRouterOutlet>
      </IonReactRouter>
    </IonApp>
  )
}

export default App
```

</$CodeTabs>

Once that's done, run this in a terminal window:

```bash
ionic serve
```

And then open the browser to [localhost:3000](http://localhost:3000) and you should see the completed app.

![Supabase Ionic React](/docs/img/ionic-demos/ionic-react.png)

## Bonus: Profile photos

Every Supabase project is configured with [Storage](/docs/guides/storage) for managing large files like photos and videos.

### Create an upload widget

First install two packages in order to interact with the user's camera.

```bash
npm install @ionic/pwa-elements @capacitor/camera
```

[Capacitor](https://capacitorjs.com) is a cross platform native runtime from Ionic that enables web apps to be deployed through the app store and provides access to native device API.

Ionic PWA elements is a companion package that will polyfill certain browser APIs that provide no user interface with custom Ionic UI.

With those packages installed we can update our `index.tsx` to include an additional bootstrapping call for the Ionic PWA Elements.

<$CodeTabs>

```ts name=src/index.tsx
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
import * as serviceWorkerRegistration from './serviceWorkerRegistration'
import reportWebVitals from './reportWebVitals'

import { defineCustomElements } from '@ionic/pwa-elements/loader'
defineCustomElements(window)

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById('root')
)

serviceWorkerRegistration.unregister()
reportWebVitals()
```

</$CodeTabs>

Then create an `AvatarComponent`.

<$CodeTabs>

```jsx name=src/components/Avatar.tsx
import { IonIcon } from '@ionic/react';
import { person } from 'ionicons/icons';
import { Camera, CameraResultType } from '@capacitor/camera';
import { useEffect, useState } from 'react';
import { supabase } from '../supabaseClient';
import './Avatar.css'
export function Avatar({
  url,
  onUpload,
}: {
  url: string;
  onUpload: (e: any, file: string) => Promise<void>;
}) {
  const [avatarUrl, setAvatarUrl] = useState<string | undefined>();

  useEffect(() => {
    if (url) {
      downloadImage(url);
    }
  }, [url]);
  const uploadAvatar = async () => {
    try {
      const photo = await Camera.getPhoto({
        resultType: CameraResultType.DataUrl,
      });

      const file = await fetch(photo.dataUrl!)
        .then((res) => res.blob())
        .then(
          (blob) =>
            new File([blob], 'my-file', { type: `image/${photo.format}` })
        );

      const fileName = `${Math.random()}-${new Date().getTime()}.${
        photo.format
      }`;
      const { error: uploadError } = await supabase.storage
        .from('avatars')
        .upload(fileName, file);
      if (uploadError) {
        throw uploadError;
      }
      onUpload(null, fileName);
    } catch (error) {
      console.log(error);
    }
  };

  const downloadImage = async (path: string) => {
    try {
      const { data, error } = await supabase.storage
        .from('avatars')
        .download(path);
      if (error) {
        throw error;
      }
      const url = URL.createObjectURL(data!);
      setAvatarUrl(url);
    } catch (error: any) {
      console.log('Error downloading image: ', error.message);
    }
  };

  return (
    <div className="avatar">
    <div className="avatar_wrapper" onClick={uploadAvatar}>
      {avatarUrl ? (
        <img src={avatarUrl} />
      ) : (
        <IonIcon icon={person} className="no-avatar" />
      )}
    </div>

    </div>
  );
}
```

</$CodeTabs>

### Add the new widget

And then we can add the widget to the Account page:

<$CodeTabs>

```jsx name=src/pages/Account.tsx
// Import the new component

import { Avatar } from '../components/Avatar';

// ...
return (
  <IonPage>
    <IonHeader>
      <IonToolbar>
        <IonTitle>Account</IonTitle>
      </IonToolbar>
    </IonHeader>

    <IonContent>
      <Avatar url={profile.avatar_url} onUpload={updateProfile}></Avatar>
```

</$CodeTabs>

At this stage you have a fully functional application!


## Links discovered
- [Supabase User Management example](https://github.com/supabase/supabase/blob/master/docs/img/ionic-demos/ionic-angular-account.png)
- [full example on GitHub](https://github.com/mhartington/supabase-ionic-react)
- [Ionic CLI](https://ionicframework.com/docs/cli)
- [supabase-js](https://github.com/supabase/supabase-js)
- [Row Level Security](https://github.com/supabase/supabase/blob/master/docs/guides/auth#row-level-security.md)
- [localhost:3000](http://localhost:3000)
- [Supabase Ionic React](https://github.com/supabase/supabase/blob/master/docs/img/ionic-demos/ionic-react.png)
- [Storage](https://github.com/supabase/supabase/blob/master/docs/guides/storage.md)
- [Capacitor](https://capacitorjs.com)

--- apps/docs/content/guides/getting-started/tutorials/with-ionic-vue.mdx ---
---
title: 'Build a User Management App with Ionic Vue'
description: 'Learn how to use Supabase in your Ionic Vue App.'
---

<$Partial path="quickstart_intro.mdx" />

![Supabase User Management example](/docs/img/ionic-demos/ionic-angular-account.png)

<Admonition type="note">

If you get stuck while working through this guide, refer to the [full example on GitHub](https://github.com/mhartington/supabase-ionic-vue).

</Admonition>

<$Partial path="project_setup.mdx" variables={{ "framework": "vuejs", "tab": "frameworks" }} />

## Building the app

Let's start building the Vue app from scratch.

### Initialize an Ionic Vue app

We can use the [Ionic CLI](https://ionicframework.com/docs/cli) to initialize an app called `supabase-ionic-vue`:

```bash
npm install -g @ionic/cli
ionic start supabase-ionic-vue blank --type vue
cd supabase-ionic-vue
```

Then let's install the only additional dependency: [supabase-js](https://github.com/supabase/supabase-js)

```bash
npm install @supabase/supabase-js
```

And finally we want to save the environment variables in a `.env`.

All we need are the API URL and the key that you copied [earlier](#get-api-details).

<$CodeTabs>

```bash name=.env
VITE_SUPABASE_URL=YOUR_SUPABASE_URL
VITE_SUPABASE_PUBLISHABLE_KEY=YOUR_SUPABASE_PUBLISHABLE_KEY
```

</$CodeTabs>

Now that we have the API credentials in place, let's create a helper file to initialize the Supabase client. These variables will be exposed on the browser, and that's completely fine since we have [Row Level Security](/docs/guides/auth#row-level-security) enabled on our Database.

<$CodeTabs>

```js name=src/supabase.ts
import { createClient } from '@supabase/supabase-js';

const supabaseUrl = import.meta.env.VITE_SUPABASE_URL as string;
const supabasePublishableKey = import.meta.env.VITE_SUPABASE_PUBLISHABLE_KEY as string;

export const supabase = createClient(supabaseUrl, supabasePublishableKey);
```

</$CodeTabs>

### Set up a login route

Let's set up a Vue component to manage logins and sign ups. We'll use Magic Links, so users can sign in with their email without using passwords.

<$CodeTabs>

```html name=/src/views/Login.vue
<template>
  <ion-page>
    <ion-header>
      <ion-toolbar>
        <ion-title>Login</ion-title>
      </ion-toolbar>
    </ion-header>

    <ion-content>
      <div class="ion-padding">
        <h1>Supabase + Ionic Vue</h1>
        <p>Sign in via magic link with your email below</p>
      </div>
      <ion-list inset="true">
        <form @submit.prevent="handleLogin">
          <ion-item>
            <ion-label position="stacked">Email</ion-label>
            <ion-input v-model="email" name="email" autocomplete type="email"></ion-input>
          </ion-item>
          <div class="ion-text-center">
            <ion-button type="submit" fill="clear">Login</ion-button>
          </div>
        </form>
      </ion-list>
      <p>{{ email }}</p>
    </ion-content>
  </ion-page>
</template>

<script lang="ts">
  import { supabase } from '../supabase'
  import {
    IonContent,
    IonHeader,
    IonPage,
    IonTitle,
    IonToolbar,
    IonList,
    IonItem,
    IonLabel,
    IonInput,
    IonButton,
    toastController,
    loadingController,
  } from '@ionic/vue'
  import { defineComponent, ref } from 'vue'

  export default defineComponent({
    name: 'LoginPage',
    components: {
      IonContent,
      IonHeader,
      IonPage,
      IonTitle,
      IonToolbar,
      IonList,
      IonItem,
      IonLabel,
      IonInput,
      IonButton,
    },
    setup() {
      const email = ref('')
      const handleLogin = async () => {
        const loader = await loadingController.create({})
        const toast = await toastController.create({ duration: 5000 })

        try {
          await loader.present()
          const { error } = await supabase.auth.signInWithOtp({ email: email.value })

          if (error) throw error

          toast.message = 'Check your email for the login link!'
          await toast.present()
        } catch (error: any) {
          toast.message = error.error_description || error.message
          await toast.present()
        } finally {
          await loader.dismiss()
        }
      }
      return { handleLogin, email }
    },
  })
</script>
```

</$CodeTabs>

### Account page

After a user is signed in we can allow them to edit their profile details and manage their account.

Let's create a new component for that called `Account.vue`.

<$CodeTabs>

```html name=src/views/Account.vue
<template>
  <ion-page>
    <ion-header>
      <ion-toolbar>
        <ion-title>Account</ion-title>
      </ion-toolbar>
    </ion-header>

    <ion-content>
      <form @submit.prevent="updateProfile">
        <ion-item>
          <ion-label>
            <p>Email</p>
            <p>{{ user?.email }}</p>
          </ion-label>
        </ion-item>

        <ion-item>
          <ion-label position="stacked">Name</ion-label>
          <ion-input type="text" v-model="profile.username" />
        </ion-item>

        <ion-item>
          <ion-label position="stacked">Website</ion-label>
          <ion-input type="url" v-model="profile.website" />
        </ion-item>

        <div class="ion-text-center">
          <ion-button type="submit" fill="clear">Update Profile</ion-button>
        </div>
      </form>

      <div class="ion-text-center">
        <ion-button fill="clear" @click="signOut">Log Out</ion-button>
      </div>
    </ion-content>
  </ion-page>
</template>

<script lang="ts">
  import {
    IonPage,
    IonHeader,
    IonToolbar,
    IonTitle,
    IonContent,
    IonItem,
    IonLabel,
    IonInput,
    IonButton,
    toastController,
    loadingController,
  } from '@ionic/vue'
  import { defineComponent, onMounted, ref } from 'vue'
  import { useRouter } from 'vue-router'
  import { supabase } from '@/supabase'
  import type { User } from '@supabase/supabase-js'

  export default defineComponent({
    name: 'AccountPage',
    components: {
      IonPage,
      IonHeader,
      IonToolbar,
      IonTitle,
      IonContent,
      IonItem,
      IonLabel,
      IonInput,
      IonButton,
    },
    setup() {
      const router = useRouter()
      const user = ref<User | null>(null)

      const profile = ref({
        username: '',
        website: '',
        avatar_url: '',
      })

      const getProfile = async () => {
        const loader = await loadingController.create()
        const toast = await toastController.create({ duration: 5000 })
        await loader.present()

        try {
          const { data, error, status } = await supabase
            .from('profiles')
            .select('username, website, avatar_url')
            .eq('id', user.value?.id)
            .single()

          if (error && status !== 406) throw error

          if (data) {
            profile.value = {
              username: data.username,
              website: data.website,
              avatar_url: data.avatar_url,
            }
          }
        } catch (error: any) {
          toast.message = error.message
          await toast.present()
        } finally {
          await loader.dismiss()
        }
      }

      const updateProfile = async () => {
        const loader = await loadingController.create()
        const toast = await toastController.create({ duration: 5000 })
        await loader.present()

        try {
          const updates = {
            id: user.value?.id,
            ...profile.value,
            updated_at: new Date(),
          }

          const { error } = await supabase.from('profiles').upsert(updates, {
            returning: 'minimal',
          })

          if (error) throw error
        } catch (error: any) {
          toast.message = error.message
          await toast.present()
        } finally {
          await loader.dismiss()
        }
      }

      const signOut = async () => {
        const loader = await loadingController.create()
        const toast = await toastController.create({ duration: 5000 })
        await loader.present()

        try {
          const { error } = await supabase.auth.signOut()
          if (error) throw error
          router.push('/')
        } catch (error: any) {
          toast.message = error.message
          await toast.present()
        } finally {
          await loader.dismiss()
        }
      }

      onMounted(async () => {
        const loader = await loadingController.create()
        await loader.present()

        const { data } = await supabase.auth.getSession()
        user.value = data.session?.user ?? null

        if (!user.value) {
          router.push('/')
        } else {
          await getProfile()
        }

        await loader.dismiss()
      })

      return {
        user,
        profile,
        updateProfile,
        signOut,
      }
    },
  })
</script>
```

</$CodeTabs>

### Launch!

Now that we have all the components in place, let's update `App.vue` and our routes:

<$CodeTabs>

```ts name=src/router.index.ts
import { createRouter, createWebHistory } from '@ionic/vue-router'
import { RouteRecordRaw } from 'vue-router'
import LoginPage from '../views/Login.vue'
import AccountPage from '../views/Account.vue'
const routes: Array<RouteRecordRaw> = [
  {
    path: '/',
    name: 'Login',
    component: LoginPage,
  },
  {
    path: '/account',
    name: 'Account',
    component: AccountPage,
  },
]

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes,
})

export default router
```

```html name=src/App.vue
<template>
  <ion-app>
    <ion-router-outlet />
  </ion-app>
</template>

<script lang="ts">
  import { IonApp, IonRouterOutlet, useIonRouter } from '@ionic/vue'
  import { defineComponent, ref, onMounted } from 'vue'
  import { supabase } from './supabase'

  export default defineComponent({
    name: 'App',
    components: {
      IonApp,
      IonRouterOutlet,
    },
    setup() {
      const router = useIonRouter()
      const user = ref(null)

      onMounted(() => {
        supabase.auth
          .getSession()
          .then((resp) => {
            user.value = resp.data.session?.user ?? null
          })
          .catch((err) => {
            console.log('Error fetching session', err)
          })

        supabase.auth.onAuthStateChange((_event, session) => {
          user.value = session?.user ?? null
        })
      })

      return { user }
    },
  })
</script>
```

</$CodeTabs>

Once that's done, run this in a terminal window:

```bash
ionic serve
```

And then open the browser to [localhost:3000](http://localhost:3000) and you should see the completed app.

![Supabase Ionic Vue](/docs/img/ionic-demos/ionic-vue.png)

## Bonus: Profile photos

Every Supabase project is configured with [Storage](/docs/guides/storage) for managing large files like photos and videos.

### Create an upload widget

First install two packages in order to interact with the user's camera.

```bash
npm install @ionic/pwa-elements @capacitor/camera
```

[Capacitor](https://capacitorjs.com) is a cross-platform native runtime from Ionic that enables web apps to be deployed through the app store and provides access to native device API.

Ionic PWA elements is a companion package that will polyfill certain browser APIs that provide no user interface with custom Ionic UI.

With those packages installed we can update our `main.ts` to include an additional bootstrapping call for the Ionic PWA Elements.

<$CodeTabs>

```ts name=src/main.tsx
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'

import { IonicVue } from '@ionic/vue'
/* Core CSS required for Ionic components to work properly */
import '@ionic/vue/css/ionic.bundle.css'

/* Theme variables */
import './theme/variables.css'

import { defineCustomElements } from '@ionic/pwa-elements/loader'
defineCustomElements(window)
const app = createApp(App).use(IonicVue).use(router)

router.isReady().then(() => {
  app.mount('#app')
})
```

</$CodeTabs>

Then create an `AvatarComponent`.

<$CodeTabs>

```html name=src/components/Avatar.vue
<template>
  <div class="avatar">
    <div class="avatar_wrapper" @click="uploadAvatar">
      <img v-if="avatarUrl" :src="avatarUrl" />
      <ion-icon v-else name="person" class="no-avatar"></ion-icon>
    </div>
  </div>
</template>

<script lang="ts">
  import { ref, toRefs, watch, defineComponent } from 'vue'
  import { supabase } from '../supabase'
  import { Camera, CameraResultType } from '@capacitor/camera'
  import { IonIcon } from '@ionic/vue'
  import { person } from 'ionicons/icons'
  export default defineComponent({
    name: 'AppAvatar',
    props: { path: String },
    emits: ['upload', 'update:path'],
    components: { IonIcon },
    setup(prop, { emit }) {
      const { path } = toRefs(prop)
      const avatarUrl = ref('')

      const downloadImage = async () => {
        try {
          const { data, error } = await supabase.storage.from('avatars').download(path.value)
          if (error) throw error
          avatarUrl.value = URL.createObjectURL(data!)
        } catch (error: any) {
          console.error('Error downloading image: ', error.message)
        }
      }

      const uploadAvatar = async () => {
        try {
          const photo = await Camera.getPhoto({
            resultType: CameraResultType.DataUrl,
          })
          if (photo.dataUrl) {
            const file = await fetch(photo.dataUrl)
              .then((res) => res.blob())
              .then((blob) => new File([blob], 'my-file', { type: `image/${photo.format}` }))

            const fileName = `${Math.random()}-${new Date().getTime()}.${photo.format}`
            const { error: uploadError } = await supabase.storage
              .from('avatars')
              .upload(fileName, file)
            if (uploadError) {
              throw uploadError
            }
            emit('update:path', fileName)
            emit('upload')
          }
        } catch (error) {
          console.log(error)
        }
      }

      watch(path, () => {
        if (path.value) downloadImage()
      })

      return { avatarUrl, uploadAvatar, person }
    },
  })
</script>
<style>
  .avatar {
    display: block;
    margin: auto;
    min-height: 150px;
  }
  .avatar .avatar_wrapper {
    margin: 16px auto 16px;
    border-radius: 50%;
    overflow: hidden;
    height: 150px;
    aspect-ratio: 1;
    background: var(--ion-color-step-50);
    border: thick solid var(--ion-color-step-200);
  }
  .avatar .avatar_wrapper:hover {
    cursor: pointer;
  }
  .avatar .avatar_wrapper ion-icon.no-avatar {
    width: 100%;
    height: 115%;
  }
  .avatar img {
    display: block;
    object-fit: cover;
    width: 100%;
    height: 100%;
  }
</style>
```

</$CodeTabs>

### Add the new widget

And then we can add the widget to the Account page:

<$CodeTabs>

```html name=src/views/Account.vue
<template>
  <ion-page>
    <ion-header>
      <ion-toolbar>
        <ion-title>Account</ion-title>
      </ion-toolbar>
    </ion-header>

    <ion-content>
      <avatar v-model:path="profile.avatar_url" @upload="updateProfile"></avatar>
...
</template>
<script lang="ts">
import Avatar from '../components/Avatar.vue';
export default defineComponent({
  name: 'AccountPage',
  components: {
    Avatar,
    ....
  }

</script>
```

</$CodeTabs>

At this stage you have a fully functional application!


## Links discovered
- [Supabase User Management example](https://github.com/supabase/supabase/blob/master/docs/img/ionic-demos/ionic-angular-account.png)
- [full example on GitHub](https://github.com/mhartington/supabase-ionic-vue)
- [Ionic CLI](https://ionicframework.com/docs/cli)
- [supabase-js](https://github.com/supabase/supabase-js)
- [Row Level Security](https://github.com/supabase/supabase/blob/master/docs/guides/auth#row-level-security.md)
- [localhost:3000](http://localhost:3000)
- [Supabase Ionic Vue](https://github.com/supabase/supabase/blob/master/docs/img/ionic-demos/ionic-vue.png)
- [Storage](https://github.com/supabase/supabase/blob/master/docs/guides/storage.md)
- [Capacitor](https://capacitorjs.com)

--- apps/docs/content/guides/getting-started/tutorials/with-kotlin.mdx ---
---
title: 'Build a Product Management Android App with Jetpack Compose'
description: 'Learn how to use Supabase in your Android Kotlin App.'
---

This tutorial demonstrates how to build a basic product management app. The app demonstrates management operations, photo upload, account creation and authentication using:

- [Supabase Database](/docs/guides/database) - a Postgres database for storing your user data and [Row Level Security](/docs/guides/auth#row-level-security) so data is protected and users can only access their own information.
- [Supabase Auth](/docs/guides/auth) - users log in through magic links sent to their email (without having to set up a password).
- [Supabase Storage](/docs/guides/storage) - users can upload a profile photo.

![manage-product-cover](/docs/img/guides/kotlin/manage-product-cover.png)

<Admonition type="note">

If you get stuck while working through this guide, refer to the [full example on GitHub](https://github.com/hieuwu/product-sample-supabase-kt).

</Admonition>

<$Partial path="kotlin_project_setup.mdx" variables={{ "framework": "androidkotlin", "tab": "mobiles" }} />

## Building the app

### Create new Android project

Open Android Studio > New Project > Base Activity (Jetpack Compose).

![Android Studio new project](/docs/img/guides/kotlin/android-studio-new-project.png)

### Set up API key and secret securely

#### Create local environment secret

Create or edit the `local.properties` file at the root (same level as `build.gradle`) of your project.

> **Note**: Do not commit this file to your source control, for example, by adding it to your `.gitignore` file!

```kotlin
SUPABASE_PUBLISHABLE_KEY=YOUR_SUPABASE_PUBLISHABLE_KEY
SUPABASE_URL=YOUR_SUPABASE_URL
```

#### Read and set value to `BuildConfig`

In your `build.gradle` (app) file, create a `Properties` object and read the values from your `local.properties` file by calling the `buildConfigField` method:

```kotlin
defaultConfig {
   applicationId "com.example.manageproducts"
   minSdkVersion 22
   targetSdkVersion 33
   versionCode 5
   versionName "1.0"
   testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

   // Set value part
   Properties properties = new Properties()
   properties.load(project.rootProject.file("local.properties").newDataInputStream())
   buildConfigField("String", "SUPABASE_PUBLISHABLE_KEY", "\"${properties.getProperty("SUPABASE_PUBLISHABLE_KEY")}\"")
   buildConfigField("String", "SECRET", "\"${properties.getProperty("SECRET")}\"")
   buildConfigField("String", "SUPABASE_URL", "\"${properties.getProperty("SUPABASE_URL")}\"")
}
```

#### Use value from `BuildConfig`

Read the value from `BuildConfig`:

```kotlin
val url = BuildConfig.SUPABASE_URL
val apiKey = BuildConfig.SUPABASE_PUBLISHABLE_KEY
```

### Set up Supabase dependencies

![Gradle dependencies](/docs/img/guides/kotlin/gradle-dependencies.png)

In the `build.gradle` (app) file, add these dependencies then press "Sync now." Replace the dependency version placeholders `$supabase_version` and `$ktor_version` with their respective latest versions.

```kotlin
implementation "io.github.jan-tennert.supabase:postgrest-kt:$supabase_version"
implementation "io.github.jan-tennert.supabase:storage-kt:$supabase_version"
implementation "io.github.jan-tennert.supabase:auth-kt:$supabase_version"
implementation "io.ktor:ktor-client-android:$ktor_version"
implementation "io.ktor:ktor-client-core:$ktor_version"
implementation "io.ktor:ktor-utils:$ktor_version"
```

Also in the `build.gradle` (app) file, add the plugin for serialization. The version of this plugin should be the same as your Kotlin version.

```kotlin
plugins {
    ...
    id 'org.jetbrains.kotlin.plugin.serialization' version '$kotlin_version'
    ...
}
```

{/* supa-mdx-lint-disable-next-line Rule001HeadingCase */}

### Set up Hilt for dependency injection

In the `build.gradle` (app) file, add the following:

```kotlin
implementation "com.google.dagger:hilt-android:$hilt_version"
annotationProcessor "com.google.dagger:hilt-compiler:$hilt_version"
implementation("androidx.hilt:hilt-navigation-compose:1.0.0")
```

Create a new `ManageProductApplication.kt` class extending Application with `@HiltAndroidApp` annotation:

```kotlin
// ManageProductApplication.kt
@HiltAndroidApp
class ManageProductApplication: Application()
```

Open the `AndroidManifest.xml` file, update name property of Application tag:

```xml
<application
...
    android:name=".ManageProductApplication"
...
</application>

```

Create the `MainActivity`:

```kotlin
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
    //This will come later
}
```

{/* supa-mdx-lint-disable-next-line Rule001HeadingCase */}

### Provide Supabase instances with Hilt

To make the app easier to test, create a `SupabaseModule.kt` file as follows:

```kotlin
@InstallIn(SingletonComponent::class)
@Module
object SupabaseModule {

    @Provides
    @Singleton
    fun provideSupabaseClient(): SupabaseClient {
        return createSupabaseClient(
            supabaseUrl = BuildConfig.SUPABASE_URL,
            supabaseKey = BuildConfig.SUPABASE_PUBLISHABLE_KEY
        ) {
            install(Postgrest)
            install(Auth) {
                flowType = FlowType.PKCE
                scheme = "app"
                host = "supabase.com"
            }
            install(Storage)
        }
    }

    @Provides
    @Singleton
    fun provideSupabaseDatabase(client: SupabaseClient): Postgrest {
        return client.postgrest
    }

    @Provides
    @Singleton
    fun provideSupabaseAuth(client: SupabaseClient): Auth {
        return client.auth
    }


    @Provides
    @Singleton
    fun provideSupabaseStorage(client: SupabaseClient): Storage {
        return client.storage
    }

}
```

### Create a data transfer object

Create a `ProductDto.kt` class and use annotations to parse data from Supabase:

```kotlin
@Serializable
data class ProductDto(

    @SerialName("name")
    val name: String,

    @SerialName("price")
    val price: Double,

    @SerialName("image")
    val image: String?,

    @SerialName("id")
    val id: String,
)
```

Create a Domain object in `Product.kt` expose the data in your view:

```kotlin
data class Product(
    val id: String,
    val name: String,
    val price: Double,
    val image: String?
)
```

### Implement repositories

Create a `ProductRepository` interface and its implementation named `ProductRepositoryImpl`. This holds the logic to interact with data sources from Supabase. Do the same with the `AuthenticationRepository`.

Create the Product Repository:

```kotlin
interface ProductRepository {
    suspend fun createProduct(product: Product): Boolean
    suspend fun getProducts(): List<ProductDto>?
    suspend fun getProduct(id: String): ProductDto
    suspend fun deleteProduct(id: String)
    suspend fun updateProduct(
        id: String, name: String, price: Double, imageName: String, imageFile: ByteArray
    )
}
```

```kotlin
class ProductRepositoryImpl @Inject constructor(
    private val postgrest: Postgrest,
    private val storage: Storage,
) : ProductRepository {
    override suspend fun createProduct(product: Product): Boolean {
        return try {
            withContext(Dispatchers.IO) {
                val productDto = ProductDto(
                    name = product.name,
                    price = product.price,
                )
                postgrest.from("products").insert(productDto)
                true
            }
            true
        } catch (e: java.lang.Exception) {
            throw e
        }
    }

    override suspend fun getProducts(): List<ProductDto>? {
        return withContext(Dispatchers.IO) {
            val result = postgrest.from("products")
                .select().decodeList<ProductDto>()
            result
        }
    }


    override suspend fun getProduct(id: String): ProductDto {
        return withContext(Dispatchers.IO) {
            postgrest.from("products").select {
                filter {
                    eq("id", id)
                }
            }.decodeSingle<ProductDto>()
        }
    }

    override suspend fun deleteProduct(id: String) {
        return withContext(Dispatchers.IO) {
            postgrest.from("products").delete {
                filter {
                    eq("id", id)
                }
            }
        }
    }

    override suspend fun updateProduct(
        id: String,
        name: String,
        price: Double,
        imageName: String,
        imageFile: ByteArray
    ) {
        withContext(Dispatchers.IO) {
            if (imageFile.isNotEmpty()) {
                val imageUrl =
                    storage.from("Product%20Image").upload(
                        path = "$imageName.png",
                        data = imageFile,
                        upsert = true
                    )
                postgrest.from("products").update({
                    set("name", name)
                    set("price", price)
                    set("image", buildImageUrl(imageFileName = imageUrl))
                }) {
                    filter {
                        eq("id", id)
                    }
                }
            } else {
                postgrest.from("products").update({
                    set("name", name)
                    set("price", price)
                }) {
                    filter {
                        eq("id", id)
                    }
                }
            }
        }
    }

    // Because I named the bucket as "Product Image" so when it turns to an url, it is "%20"
    // For better approach, you should create your bucket name without space symbol
    private fun buildImageUrl(imageFileName: String) =
        "${BuildConfig.SUPABASE_URL}/storage/v1/object/public/${imageFileName}".replace(" ", "%20")
}
```

Create the Authentication Repository:

```kotlin
interface AuthenticationRepository {
    suspend fun signIn(email: String, password: String): Boolean
    suspend fun signUp(email: String, password: String): Boolean
    suspend fun signInWithGoogle(): Boolean
}
```

```kotlin
class AuthenticationRepositoryImpl @Inject constructor(
    private val auth: Auth
) : AuthenticationRepository {
    override suspend fun signIn(email: String, password: String): Boolean {
        return try {
            auth.signInWith(Email) {
                this.email = email
                this.password = password
            }
            true
        } catch (e: Exception) {
            false
        }
    }

    override suspend fun signUp(email: String, password: String): Boolean {
        return try {
            auth.signUpWith(Email) {
                this.email = email
                this.password = password
            }
            true
        } catch (e: Exception) {
            false
        }
    }

    override suspend fun signInWithGoogle(): Boolean {
        return try {
            auth.signInWith(Google)
            true
        } catch (e: Exception) {
            false
        }
    }
}
```

### Implement screens

To navigate screens, use the AndroidX navigation library. For routes, implement a `Destination` interface:

```kotlin

interface Destination {
    val route: String
    val title: String
}


object ProductListDestination : Destination {
    override val route = "product_list"
    override val title = "Product List"
}

object ProductDetailsDestination : Destination {
    override val route = "product_details"
    override val title = "Product Details"
    const val productId = "product_id"
    val arguments = listOf(navArgument(name = productId) {
        type = NavType.StringType
    })
    fun createRouteWithParam(productId: String) = "$route/${productId}"
}

object AddProductDestination : Destination {
    override val route = "add_product"
    override val title = "Add Product"
}

object AuthenticationDestination: Destination {
    override val route = "authentication"
    override val title = "Authentication"
}

object SignUpDestination: Destination {
    override val route = "signup"
    override val title = "Sign Up"
}
```

This will help later for navigating between screens.

Create a `ProductListViewModel`:

```kotlin
@HiltViewModel
class ProductListViewModel @Inject constructor(
private val productRepository: ProductRepository,
) : ViewModel() {

    private val _productList = MutableStateFlow<List<Product>?>(listOf())
    val productList: Flow<List<Product>?> = _productList


    private val _isLoading = MutableStateFlow(false)
    val isLoading: Flow<Boolean> = _isLoading

    init {
        getProducts()
    }

    fun getProducts() {
        viewModelScope.launch {
            val products = productRepository.getProducts()
            _productList.emit(products?.map { it -> it.asDomainModel() })
        }
    }

    fun removeItem(product: Product) {
        viewModelScope.launch {
            val newList = mutableListOf<Product>().apply { _productList.value?.let { addAll(it) } }
            newList.remove(product)
            _productList.emit(newList.toList())
            // Call api to remove
            productRepository.deleteProduct(id = product.id)
            // Then fetch again
            getProducts()
        }
    }

    private fun ProductDto.asDomainModel(): Product {
        return Product(
            id = this.id,
            name = this.name,
            price = this.price,
            image = this.image
        )
    }

}

```

Create the `ProductListScreen.kt`:

```kotlin
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class)
@Composable
fun ProductListScreen(
    modifier: Modifier = Modifier,
    navController: NavController,
    viewModel: ProductListViewModel = hiltViewModel(),
) {
    val isLoading by viewModel.isLoading.collectAsState(initial = false)
    val swipeRefreshState = rememberSwipeRefreshState(isRefreshing = isLoading)
    SwipeRefresh(state = swipeRefreshState, onRefresh = { viewModel.getProducts() }) {
        Scaffold(
            topBar = {
                TopAppBar(
                    backgroundColor = MaterialTheme.colorScheme.primary,
                    title = {
                        Text(
                            text = stringResource(R.string.product_list_text_screen_title),
                            color = MaterialTheme.colorScheme.onPrimary,
                        )
                    },
                )
            },
            floatingActionButton = {
                AddProductButton(onClick = { navController.navigate(AddProductDestination.route) })
            }
        ) { padding ->
            val productList = viewModel.productList.collectAsState(initial = listOf()).value
            if (!productList.isNullOrEmpty()) {
                LazyColumn(
                    modifier = modifier.padding(padding),
                    contentPadding = PaddingValues(5.dp)
                ) {
                    itemsIndexed(
                        items = productList,
                        key = { _, product -> product.name }) { _, item ->
                        val state = rememberDismissState(
                            confirmStateChange = {
                                if (it == DismissValue.DismissedToStart) {
                                    // Handle item removed
                                    viewModel.removeItem(item)
                                }
                                true
                            }
                        )
                        SwipeToDismiss(
                            state = state,
                            background = {
                                val color by animateColorAsState(
                                    targetValue = when (state.dismissDirection) {
                                        DismissDirection.StartToEnd -> MaterialTheme.colorScheme.primary
                                        DismissDirection.EndToStart -> MaterialTheme.colorScheme.primary.copy(
                                            alpha = 0.2f
                                        )
                                        null -> Color.Transparent
                                    }
                                )
                                Box(
                                    modifier = modifier
                                        .fillMaxSize()
                                        .background(color = color)
                                        .padding(16.dp),
                                ) {
                                    Icon(
                                        imageVector = Icons.Filled.Delete,
                                        contentDescription = null,
                                        tint = MaterialTheme.colorScheme.primary,
                                        modifier = modifier.align(Alignment.CenterEnd)
                                    )
                                }

                            },
                            dismissContent = {
                                ProductListItem(
                                    product = item,
                                    modifier = modifier,
                                    onClick = {
                                        navController.navigate(
                                            ProductDetailsDestination.createRouteWithParam(
                                                item.id
                                            )
                                        )
                                    },
                                )
                            },
                            directions = setOf(DismissDirection.EndToStart),
                        )
                    }
                }
            } else {
                Text("Product list is empty!")
            }

        }
    }
}

@Composable
private fun AddProductButton(
    modifier: Modifier = Modifier,
    onClick: () -> Unit,
) {
    FloatingActionButton(
        modifier = modifier,
        onClick = onClick,
        containerColor = MaterialTheme.colorScheme.primary,
        contentColor = MaterialTheme.colorScheme.onPrimary
    ) {
        Icon(
            imageVector = Icons.Filled.Add,
            contentDescription = null,
        )
    }
}
```

Create the `ProductDetailsViewModel.kt`:

```kotlin

@HiltViewModel
class ProductDetailsViewModel @Inject constructor(
    private val productRepository: ProductRepository,
    savedStateHandle: SavedStateHandle,
    ) : ViewModel() {

    private val _product = MutableStateFlow<Product?>(null)
    val product: Flow<Product?> = _product

    private val _name = MutableStateFlow("")
    val name: Flow<String> = _name

    private val _price = MutableStateFlow(0.0)
    val price: Flow<Double> = _price

    private val _imageUrl = MutableStateFlow("")
    val imageUrl: Flow<String> = _imageUrl

    init {
        val productId = savedStateHandle.get<String>(ProductDetailsDestination.productId)
        productId?.let {
            getProduct(productId = it)
        }
    }

    private fun getProduct(productId: String) {
        viewModelScope.launch {
           val result = productRepository.getProduct(productId).asDomainModel()
            _product.emit(result)
            _name.emit(result.name)
            _price.emit(result.price)
        }
    }

    fun onNameChange(name: String) {
        _name.value = name
    }

    fun onPriceChange(price: Double) {
        _price.value = price
    }

    fun onSaveProduct(image: ByteArray) {
        viewModelScope.launch {
            productRepository.updateProduct(
                id = _product.value?.id,
                price = _price.value,
                name = _name.value,
                imageFile = image,
                imageName = "image_${_product.value.id}",
            )
        }
    }

    fun onImageChange(url: String) {
        _imageUrl.value = url
    }

    private fun ProductDto.asDomainModel(): Product {
        return Product(
            id = this.id,
            name = this.name,
            price = this.price,
            image = this.image
        )
    }
}
```

Create the `ProductDetailsScreen.kt`:

```kotlin
@OptIn(ExperimentalCoilApi::class)
@SuppressLint("UnusedMaterialScaffoldPaddingParameter")
@Composable
fun ProductDetailsScreen(
    modifier: Modifier = Modifier,
    viewModel: ProductDetailsViewModel = hiltViewModel(),
    navController: NavController,
    productId: String?,
) {
    val snackBarHostState = remember { SnackbarHostState() }
    val coroutineScope = rememberCoroutineScope()

    Scaffold(
        snackbarHost = { SnackbarHost(snackBarHostState) },
        topBar = {
            TopAppBar(
                navigationIcon = {
                    IconButton(onClick = {
                        navController.navigateUp()
                    }) {
                        Icon(
                            imageVector = Icons.Filled.ArrowBack,
                            contentDescription = null,
                            tint = MaterialTheme.colorScheme.onPrimary
                        )
                    }
                },
                backgroundColor = MaterialTheme.colorScheme.primary,
                title = {
                    Text(
                        text = stringResource(R.string.product_details_text_screen_title),
                        color = MaterialTheme.colorScheme.onPrimary,
                    )
                },
            )
        }
    ) {
        val name = viewModel.name.collectAsState(initial = "")
        val price = viewModel.price.collectAsState(initial = 0.0)
        var imageUrl = Uri.parse(viewModel.imageUrl.collectAsState(initial = null).value)
        val contentResolver = LocalContext.current.contentResolver

        Column(
            modifier = modifier
                .padding(16.dp)
                .fillMaxSize()
        ) {
            val galleryLauncher =
                rememberLauncherForActivityResult(ActivityResultContracts.GetContent())
                { uri ->
                    uri?.let {
                        if (it.toString() != imageUrl.toString()) {
                            viewModel.onImageChange(it.toString())
                        }
                    }
                }

            Image(
                painter = rememberImagePainter(imageUrl),
                contentScale = ContentScale.Fit,
                contentDescription = null,
                modifier = Modifier
                    .padding(16.dp, 8.dp)
                    .size(100.dp)
                    .align(Alignment.CenterHorizontally)
            )
            IconButton(modifier = modifier.align(alignment = Alignment.CenterHorizontally),
                onClick = {
                    galleryLauncher.launch("image/*")
                }) {
                Icon(
                    imageVector = Icons.Filled.Edit,
                    contentDescription = null,
                    tint = MaterialTheme.colorScheme.primary
                )
            }
            OutlinedTextField(
                label = {
                    Text(
                        text = "Product name",
                        color = MaterialTheme.colorScheme.primary,
                        style = MaterialTheme.typography.titleMedium
                    )
                },
                maxLines = 2,
                shape = RoundedCornerShape(32),
                modifier = modifier.fillMaxWidth(),
                value = name.value,
                onValueChange = {
                    viewModel.onNameChange(it)
                },
            )
            Spacer(modifier = modifier.height(12.dp))
            OutlinedTextField(
                label = {
                    Text(
                        text = "Product price",
                        color = MaterialTheme.colorScheme.primary,
                        style = MaterialTheme.typography.titleMedium
                    )
                },
                maxLines = 2,
                shape = RoundedCornerShape(32),
                modifier = modifier.fillMaxWidth(),
                value = price.value.toString(),
                keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
                onValueChange = {
                    viewModel.onPriceChange(it.toDouble())
                },
            )
            Spacer(modifier = modifier.weight(1f))
            Button(
                modifier = modifier.fillMaxWidth(),
                onClick = {
                    if (imageUrl.host?.contains("supabase") == true) {
                        viewModel.onSaveProduct(image = byteArrayOf())
                    } else {
                        val image = uriToByteArray(contentResolver, imageUrl)
                        viewModel.onSaveProduct(image = image)
                    }
                    coroutineScope.launch {
                        snackBarHostState.showSnackbar(
                            message = "Product updated successfully !",
                            duration = SnackbarDuration.Short
                        )
                    }
                }) {
                Text(text = "Save changes")
            }
            Spacer(modifier = modifier.height(12.dp))
            OutlinedButton(
                modifier = modifier
                    .fillMaxWidth(),
                onClick = {
                    navController.navigateUp()
                }) {
                Text(text = "Cancel")
            }

        }

    }
}


private fun getBytes(inputStream: InputStream): ByteArray {
    val byteBuffer = ByteArrayOutputStream()
    val bufferSize = 1024
    val buffer = ByteArray(bufferSize)
    var len = 0
    while (inputStream.read(buffer).also { len = it } != -1) {
        byteBuffer.write(buffer, 0, len)
    }
    return byteBuffer.toByteArray()
}


private fun uriToByteArray(contentResolver: ContentResolver, uri: Uri): ByteArray {
    if (uri == Uri.EMPTY) {
        return byteArrayOf()
    }
    val inputStream = contentResolver.openInputStream(uri)
    if (inputStream != null) {
        return getBytes(inputStream)
    }
    return byteArrayOf()
}
```

Create a `AddProductScreen`:

```kotlin
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun AddProductScreen(
    modifier: Modifier = Modifier,
    navController: NavController,
    viewModel: AddProductViewModel = hiltViewModel(),
) {
    Scaffold(
        topBar = {
            TopAppBar(
                navigationIcon = {
                    IconButton(onClick = {
                        navController.navigateUp()
                    }) {
                        Icon(
                            imageVector = Icons.Filled.ArrowBack,
                            contentDescription = null,
                            tint = MaterialTheme.colorScheme.onPrimary
                        )
                    }
                },
                backgroundColor = MaterialTheme.colorScheme.primary,
                title = {
                    Text(
                        text = stringResource(R.string.add_product_text_screen_title),
                        color = MaterialTheme.colorScheme.onPrimary,
                    )
                },
            )
        }
    ) { padding ->
        val navigateAddProductSuccess =
            viewModel.navigateAddProductSuccess.collectAsState(initial = null).value
        val isLoading =
            viewModel.isLoading.collectAsState(initial = null).value
        if (isLoading == true) {
            LoadingScreen(message = "Adding Product",
                onCancelSelected = {
                    navController.navigateUp()
                })
        } else {
            SuccessScreen(
                message = "Product added",
                onMoreAction = {
                    viewModel.onAddMoreProductSelected()
                },
                onNavigateBack = {
                    navController.navigateUp()
                })
        }

    }
}
```

Create the `AddProductViewModel.kt`:

```kotlin
@HiltViewModel
class AddProductViewModel @Inject constructor(
    private val productRepository: ProductRepository,
) : ViewModel() {

    private val _isLoading = MutableStateFlow(false)
    val isLoading: Flow<Boolean> = _isLoading

    private val _showSuccessMessage = MutableStateFlow(false)
    val showSuccessMessage: Flow<Boolean> = _showSuccessMessage

    fun onCreateProduct(name: String, price: Double) {
        if (name.isEmpty() || price <= 0) return
        viewModelScope.launch {
            _isLoading.value = true
            val product = Product(
                id = UUID.randomUUID().toString(),
                name = name,
                price = price,
            )
            productRepository.createProduct(product = product)
            _isLoading.value = false
            _showSuccessMessage.emit(true)

        }
    }
}
```

Create a `SignUpViewModel`:

```kotlin
@HiltViewModel
class SignUpViewModel @Inject constructor(
    private val authenticationRepository: AuthenticationRepository
) : ViewModel() {

    private val _email = MutableStateFlow("")
    val email: Flow<String> = _email

    private val _password = MutableStateFlow("")
    val password = _password

    fun onEmailChange(email: String) {
        _email.value = email
    }

    fun onPasswordChange(password: String) {
        _password.value = password
    }

    fun onSignUp() {
        viewModelScope.launch {
            authenticationRepository.signUp(
                email = _email.value,
                password = _password.value
            )
        }
    }
}
```

Create the `SignUpScreen.kt`:

```kotlin
@Composable
fun SignUpScreen(
    modifier: Modifier = Modifier,
    navController: NavController,
    viewModel: SignUpViewModel = hiltViewModel()
) {
    val snackBarHostState = remember { SnackbarHostState() }
    val coroutineScope = rememberCoroutineScope()
    Scaffold(
        snackbarHost = { androidx.compose.material.SnackbarHost(snackBarHostState) },
        topBar = {
            TopAppBar(
                navigationIcon = {
                    IconButton(onClick = {
                        navController.navigateUp()
                    }) {
                        Icon(
                            imageVector = Icons.Filled.ArrowBack,
                            contentDescription = null,
                            tint = MaterialTheme.colorScheme.onPrimary
                        )
                    }
                },
                backgroundColor = MaterialTheme.colorScheme.primary,
                title = {
                    Text(
                        text = "Sign Up",
                        color = MaterialTheme.colorScheme.onPrimary,
                    )
                },
            )
        }
    ) { paddingValues ->
        Column(
            modifier = modifier
                .padding(paddingValues)
                .padding(20.dp)
        ) {
            val email = viewModel.email.collectAsState(initial = "")
            val password = viewModel.password.collectAsState()
            OutlinedTextField(
                label = {
                    Text(
                        text = "Email",
                        color = MaterialTheme.colorScheme.primary,
                        style = MaterialTheme.typography.titleMedium
                    )
                },
                maxLines = 1,
                shape = RoundedCornerShape(32),
                modifier = modifier.fillMaxWidth(),
                value = email.value,
                onValueChange = {
                    viewModel.onEmailChange(it)
                },
            )
            OutlinedTextField(
                label = {
                    Text(
                        text = "Password",
                        color = MaterialTheme.colorScheme.primary,
                        style = MaterialTheme.typography.titleMedium
                    )
                },
                maxLines = 1,
                shape = RoundedCornerShape(32),
                modifier = modifier
                    .fillMaxWidth()
                    .padding(top = 12.dp),
                value = password.value,
                onValueChange = {
                    viewModel.onPasswordChange(it)
                },
            )
            val localSoftwareKeyboardController = LocalSoftwareKeyboardController.current
            Button(modifier = modifier
                .fillMaxWidth()
                .padding(top = 12.dp),
                onClick = {
                    localSoftwareKeyboardController?.hide()
                    viewModel.onSignUp()
                    coroutineScope.launch {
                        snackBarHostState.showSnackbar(
                            message = "Create account successfully. Sign in now!",
                            duration = SnackbarDuration.Long
                        )
                    }
                }) {
                Text("Sign up")
            }
        }
    }
}
```

Create a `SignInViewModel`:

```kotlin
@HiltViewModel
class SignInViewModel @Inject constructor(
    private val authenticationRepository: AuthenticationRepository
) : ViewModel() {

    private val _email = MutableStateFlow("")
    val email: Flow<String> = _email

    private val _password = MutableStateFlow("")
    val password = _password

    fun onEmailChange(email: String) {
        _email.value = email
    }

    fun onPasswordChange(password: String) {
        _password.value = password
    }

    fun onSignIn() {
        viewModelScope.launch {
            authenticationRepository.signIn(
                email = _email.value,
                password = _password.value
            )
        }
    }

    fun onGoogleSignIn() {
        viewModelScope.launch {
            authenticationRepository.signInWithGoogle()
        }
    }

}
```

Create the `SignInScreen.kt`:

```kotlin
@OptIn(ExperimentalMaterial3Api::class, ExperimentalComposeUiApi::class)
@Composable
fun SignInScreen(
    modifier: Modifier = Modifier,
    navController: NavController,
    viewModel: SignInViewModel = hiltViewModel()
) {
    val snackBarHostState = remember { SnackbarHostState() }
    val coroutineScope = rememberCoroutineScope()
    Scaffold(
        snackbarHost = { androidx.compose.material.SnackbarHost(snackBarHostState) },
        topBar = {
            TopAppBar(
                navigationIcon = {
                    IconButton(onClick = {
                        navController.navigateUp()
                    }) {
                        Icon(
                            imageVector = Icons.Filled.ArrowBack,
                            contentDescription = null,
                            tint = MaterialTheme.colorScheme.onPrimary
                        )
                    }
                },
                backgroundColor = MaterialTheme.colorScheme.primary,
                title = {
                    Text(
                        text = "Login",
                        color = MaterialTheme.colorScheme.onPrimary,
                    )
                },
            )
        }
    ) { paddingValues ->
        Column(
            modifier = modifier
                .padding(paddingValues)
                .padding(20.dp)
        ) {
            val email = viewModel.email.collectAsState(initial = "")
            val password = viewModel.password.collectAsState()
            androidx.compose.material.OutlinedTextField(
                label = {
                    Text(
                        text = "Email",
                        color = MaterialTheme.colorScheme.primary,
                        style = MaterialTheme.typography.titleMedium
                    )
                },
                maxLines = 1,
                shape = RoundedCornerShape(32),
                modifier = modifier.fillMaxWidth(),
                value = email.value,
                onValueChange = {
                    viewModel.onEmailChange(it)
                },
            )
            androidx.compose.material.OutlinedTextField(
                label = {
                    Text(
                        text = "Password",
                        color = MaterialTheme.colorScheme.primary,
                        style = MaterialTheme.typography.titleMedium
                    )
                },
                maxLines = 1,
                shape = RoundedCornerShape(32),
                modifier = modifier
                    .fillMaxWidth()
                    .padding(top = 12.dp),
                value = password.value,
                onValueChange = {
                    viewModel.onPasswordChange(it)
                },
            )
            val localSoftwareKeyboardController = LocalSoftwareKeyboardController.current
            Button(modifier = modifier
                .fillMaxWidth()
                .padding(top = 12.dp),
                onClick = {
                    localSoftwareKeyboardController?.hide()
                    viewModel.onGoogleSignIn()
                }) {
                Text("Sign in with Google")
            }
            Button(modifier = modifier
                .fillMaxWidth()
                .padding(top = 12.dp),
                onClick = {
                    localSoftwareKeyboardController?.hide()
                    viewModel.onSignIn()
                    coroutineScope.launch {
                        snackBarHostState.showSnackbar(
                            message = "Sign in successfully !",
                            duration = SnackbarDuration.Long
                        )
                    }
                }) {
                Text("Sign in")
            }
            OutlinedButton(modifier = modifier
                .fillMaxWidth()
                .padding(top = 12.dp), onClick = {
                navController.navigate(SignUpDestination.route)
            }) {
                Text("Sign up")
            }
        }
    }
}
```

### Implement the `MainActivity`

In the `MainActivity` you created earlier, show your newly created screens:

```kotlin
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
    @Inject
    lateinit var supabaseClient: SupabaseClient

    @OptIn(ExperimentalMaterial3Api::class)
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            ManageProductsTheme {
                // A surface container using the 'background' color from the theme
                val navController = rememberNavController()
                val currentBackStack by navController.currentBackStackEntryAsState()
                val currentDestination = currentBackStack?.destination
                Scaffold { innerPadding ->
                    NavHost(
                        navController,
                        startDestination = ProductListDestination.route,
                        Modifier.padding(innerPadding)
                    ) {
                        composable(ProductListDestination.route) {
                            ProductListScreen(
                                navController = navController
                            )
                        }

                        composable(AuthenticationDestination.route) {
                            SignInScreen(
                                navController = navController
                            )
                        }

                        composable(SignUpDestination.route) {
                            SignUpScreen(
                                navController = navController
                            )
                        }

                        composable(AddProductDestination.route) {
                            AddProductScreen(
                                navController = navController
                            )
                        }

                        composable(
                            route = "${ProductDetailsDestination.route}/{${ProductDetailsDestination.productId}}",
                            arguments = ProductDetailsDestination.arguments
                        ) { navBackStackEntry ->
                            val productId =
                                navBackStackEntry.arguments?.getString(ProductDetailsDestination.productId)
                            ProductDetailsScreen(
                                productId = productId,
                                navController = navController,
                            )
                        }
                    }
                }
            }
        }
    }
}
```

### Create the success screen

To handle OAuth and OTP signins, create a new activity to handle the deep link you set in `AndroidManifest.xml`:

```xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">
    <uses-permission android:name="android.permission.INTERNET" />
    <application
        android:name=".ManageProductApplication"
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:enableOnBackInvokedCallback="true"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/Theme.ManageProducts"
        tools:targetApi="31">
        <activity
            android:name=".DeepLinkHandlerActivity"
            android:exported="true"
            android:theme="@style/Theme.ManageProducts" >
            <intent-filter android:autoVerify="true">
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />
                <data
                    android:host="supabase.com"
                    android:scheme="app" />
            </intent-filter>
        </activity>
        <activity
            android:name=".MainActivity"
            android:exported="true"
            android:label="@string/app_name"
            android:theme="@style/Theme.ManageProducts">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>
```

Then create the `DeepLinkHandlerActivity`:

```kotlin
@AndroidEntryPoint
class DeepLinkHandlerActivity : ComponentActivity() {

    @Inject
    lateinit var supabaseClient: SupabaseClient

    private lateinit var callback: (String, String) -> Unit

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        supabaseClient.handleDeeplinks(intent = intent,
            onSessionSuccess = { userSession ->
                Log.d("LOGIN", "Log in successfully with user info: ${userSession.user}")
                userSession.user?.apply {
                    callback(email ?: "", createdAt.toString())
                }
            })
        setContent {
            val navController = rememberNavController()
            val emailState = remember { mutableStateOf("") }
            val createdAtState = remember { mutableStateOf("") }
            LaunchedEffect(Unit) {
                callback = { email, created ->
                    emailState.value = email
                    createdAtState.value = created
                }
            }
            ManageProductsTheme {
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background
                ) {
                    SignInSuccessScreen(
                        modifier = Modifier.padding(20.dp),
                        navController = navController,
                        email = emailState.value,
                        createdAt = createdAtState.value,
                        onClick = { navigateToMainApp() }
                    )
                }
            }
        }
    }

    private fun navigateToMainApp() {
        val intent = Intent(this, MainActivity::class.java).apply {
            flags = Intent.FLAG_ACTIVITY_CLEAR_TOP
        }
        startActivity(intent)
    }
}
```


## Links discovered
- [Supabase Database](https://github.com/supabase/supabase/blob/master/docs/guides/database.md)
- [Row Level Security](https://github.com/supabase/supabase/blob/master/docs/guides/auth#row-level-security.md)
- [Supabase Auth](https://github.com/supabase/supabase/blob/master/docs/guides/auth.md)
- [Supabase Storage](https://github.com/supabase/supabase/blob/master/docs/guides/storage.md)
- [manage-product-cover](https://github.com/supabase/supabase/blob/master/docs/img/guides/kotlin/manage-product-cover.png)
- [full example on GitHub](https://github.com/hieuwu/product-sample-supabase-kt)
- [Android Studio new project](https://github.com/supabase/supabase/blob/master/docs/img/guides/kotlin/android-studio-new-project.png)
- [Gradle dependencies](https://github.com/supabase/supabase/blob/master/docs/img/guides/kotlin/gradle-dependencies.png)

--- apps/docs/content/guides/getting-started/tutorials/with-nextjs.mdx ---
---
title: 'Build a User Management App with Next.js'
description: 'Learn how to use Supabase in your Next.js App.'
---

<$Partial path="uiLibCta.mdx" />
<$Partial path="quickstart_intro.mdx" />

![Supabase User Management example](/docs/img/user-management-demo.png)

<Admonition type="note">

If you get stuck while working through this guide, refer to the [full example on GitHub](https://github.com/supabase/supabase/tree/master/examples/user-management/nextjs-user-management).

</Admonition>

<$Partial path="project_setup.mdx" variables={{ "framework": "nextjs", "tab": "frameworks" }} />

## Building the app

Start building the Next.js app from scratch.

### Initialize a Next.js app

Use [`create-next-app`](https://nextjs.org/docs/getting-started) to initialize an app called `supabase-nextjs`:

```bash
npx create-next-app@latest --ts --use-npm supabase-nextjs
cd supabase-nextjs
```

Then install the Supabase client library: [supabase-js](https://github.com/supabase/supabase-js)

```bash
npm install @supabase/supabase-js
```

Save the environment variables in a `.env.local` file at the root of the project, and paste the API URL and the key that you copied [earlier](#get-api-details).

```bash .env.local
NEXT_PUBLIC_SUPABASE_URL=YOUR_SUPABASE_URL
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY=YOUR_SUPABASE_PUBLISHABLE_KEY
```

### App styling (optional)

An optional step is to update the CSS file `app/globals.css` to make the app look nice.
You can find the full contents of this file [in the example repository](https://raw.githubusercontent.com/supabase/supabase/master/examples/user-management/nextjs-user-management/app/globals.css).

### Supabase Server-Side Auth

Next.js is a highly versatile framework offering pre-rendering at build time (SSG), server-side rendering at request time (SSR), API routes, and proxy edge-functions.

To better integrate with the framework, we've created the `@supabase/ssr` package for Server-Side Auth. It has all the functionalities to quickly configure your Supabase project to use cookies for storing user sessions. Read the [Next.js Server-Side Auth guide](/docs/guides/auth/server-side/nextjs) for more information.

Install the package for Next.js.

```bash
npm install @supabase/ssr
```

### Supabase utilities

There are two different types of clients in Supabase:

1. **Client Component client** - To access Supabase from Client Components, which run in the browser.
2. **Server Component client** - To access Supabase from Server Components, Server Actions, and Route Handlers, which run only on the server.

It is recommended to create the following essential utilities files for creating clients, and organize them within `lib/supabase` at the root of the project.

Create a `client.ts` and a `server.ts` with the following functionalities for client-side Supabase and server-side Supabase, respectively.

<$CodeTabs>

<$CodeSample
path="/user-management/nextjs-user-management/lib/supabase/client.ts"
lines={[[1, -1]]}
meta="name=lib/supabase/client.ts"
/>

<$CodeSample
path="/user-management/nextjs-user-management/lib/supabase/server.ts"
lines={[[1, -1]]}
meta="name=lib/supabase/server.ts"
/>

</$CodeTabs>

### Next.js proxy

Since Server Components can't write cookies, you need [Proxy](https://nextjs.org/docs/app/getting-started/proxy) to refresh expired Auth tokens and store them. This is accomplished by:

- Refreshing the Auth token with the call to `supabase.auth.getUser`.
- Passing the refreshed Auth token to Server Components through `request.cookies.set`, so they don't attempt to refresh the same token themselves.
- Passing the refreshed Auth token to the browser, so it replaces the old token. This is done with `response.cookies.set`.

You could also add a matcher, so that the Proxy only runs on routes that access Supabase. For more information, read [the Next.js matcher documentation](https://nextjs.org/docs/app/api-reference/file-conventions/proxy#matcher).

<Admonition type="danger">

Be careful when protecting pages. The server gets the user session from the cookies, which anyone can spoof.

Always use `supabase.auth.getUser()` to protect pages and user data.

_Never_ trust `supabase.auth.getSession()` inside server code such as proxy. It isn't guaranteed to revalidate the Auth token.

It's safe to trust `getUser()` because it sends a request to the Supabase Auth server every time to revalidate the Auth token.

</Admonition>

Create a `proxy.ts` file at the project root and another one within the `lib/supabase` folder. The `lib/supabase` file contains the logic for updating the session. This is used by the `proxy.ts` file, which is a Next.js convention.

<$CodeTabs>

<$CodeSample
path="/user-management/nextjs-user-management/proxy.ts"
lines={[[1, -1]]}
meta="name=proxy.ts"
/>

<$CodeSample
path="/user-management/nextjs-user-management/lib/supabase/proxy.ts"
lines={[[1, -1]]}
meta="name=lib/supabase/proxy.ts"
/>

</$CodeTabs>

## Set up a login page

### Login and signup form

In order to add login/signup page for your application:

Create a new folder named `login`, containing a `page.tsx` file with a login/signup form.

<$CodeTabs>

<$CodeSample
path="/user-management/nextjs-user-management/app/login/page.tsx"
lines={[[1, -1]]}
meta="name=app/login/page.tsx"
/>

</$CodeTabs>

Next, you need to create the login/signup actions to hook up the form to the function. Which does the following:

- Retrieve the user's information.
- Send that information to Supabase as a signup request, which in turns sends a confirmation email.
- Handle any error that arises.

<Admonition type="caution">

The `cookies` method is called before any calls to Supabase, which takes fetch calls out of Next.js's caching. This is important for authenticated data fetches, to ensure that users get access only to their own data.

Read the Next.js docs to learn more about [opting out of data caching](https://nextjs.org/docs/app/building-your-application/data-fetching/fetching-caching-and-revalidating#opting-out-of-data-caching).

</Admonition>

Create the `action.ts` file in the `app/login` folder, which contains the login and signup functions and the `error/page.tsx` file, which displays an error message if the login or signup fails.

<$CodeTabs>

<$CodeSample
path="/user-management/nextjs-user-management/app/login/actions.ts"
lines={[[1, -1]]}
meta="name=app/login/actions.ts"
/>

<$CodeSample
path="/user-management/nextjs-user-management/app/error/page.tsx"
lines={[[1, -1]]}
meta="name=app/error/page.tsx"
/>

</$CodeTabs>

### Email template

Before proceeding, change the email template to support support a server-side authentication flow that sends a token hash:

- Go to the [Auth templates](/dashboard/project/_/auth/templates) page in your dashboard.
- Select the **Confirm signup** template.
- Change `{{ .ConfirmationURL }}` to `{{ .SiteURL }}/auth/confirm?token_hash={{ .TokenHash }}&type=email`.

<Admonition type="tip">

**Did you know?** You can also customize other emails sent out to new users, including the email's looks, content, and query parameters. Check out the [settings of your project](/dashboard/project/_/auth/templates).

</Admonition>

### Confirmation endpoint

As you are working in a server-side rendering (SSR) environment, you need to create a server endpoint responsible for exchanging the `token_hash` for a session.

The code performs the following steps:

- Retrieves the code sent back from the Supabase Auth server using the `token_hash` query parameter.
- Exchanges this code for a session, which you store in your chosen storage mechanism (in this case, cookies).
- Finally, redirects the user to the `account` page.

<$CodeSample
path="/user-management/nextjs-user-management/app/auth/confirm/route.ts"
lines={[[1, -1]]}
meta="name=app/auth/confirm/route.ts"
/>

### Account page

After a user signs in, allow them to edit their profile details and manage their account.

Create a new component for that called `AccountForm` within the `app/account` folder.

<$CodeSample
path="/user-management/nextjs-user-management/app/account/account-form.tsx"
lines={[[1, 4], [7, 78], [88, -1]]}
meta="name=app/account/account-form.tsx"
/>

Create an account page for the `AccountForm` component you just created

<$CodeSample
path="/user-management/nextjs-user-management/app/account/page.tsx"
lines={[[1, -1]]}
meta="name=app/account/page.tsx"
/>

### Sign out

Create a route handler to handle the sign out from the server side, making sure to check if the user is logged in first.

<$CodeSample
path="/user-management/nextjs-user-management/app/auth/signout/route.ts"
lines={[[1, -1]]}
meta="name=app/auth/signout/route.ts"
/>

### Launch

Now you have all the pages, route handlers, and components in place, run the following in a terminal window:

```bash
npm run dev
```

And then open the browser to [localhost:3000/login](http://localhost:3000/login) and you should see the completed app.

When you enter your email and password, you will receive an email with the title **Confirm Your Signup**. Congrats 🎉!!!

## Bonus: Profile photos

Every Supabase project is configured with [Storage](/docs/guides/storage) for managing large files like
photos and videos.

### Create an upload widget

Create an avatar widget for the user so that they can upload a profile photo. Start by creating a new component:

<$CodeSample
path="/user-management/nextjs-user-management/app/account/avatar.tsx"
lines={[[1, -1]]}
meta="name=app/account/avatar.tsx"
/>

### Add the new widget

Then add the widget to the `AccountForm` component:

<$CodeSample
path="/user-management/nextjs-user-management/app/account/account-form.tsx"
lines={[[5, 5], [77, 87], [137, -1]]}
meta="name=app/account/account-form.tsx"
/>

At this stage you have a fully functional application!

## See also

- See the complete [example on GitHub](https://github.com/supabase/supabase/tree/master/examples/user-management/nextjs-user-management) and deploy it to Vercel
- [Build a Twitter Clone with the Next.js App Router and Supabase - free egghead course](https://egghead.io/courses/build-a-twitter-clone-with-the-next-js-app-router-and-supabase-19bebadb)
- Explore the [pre-built Auth components](/ui/docs/nextjs/password-based-auth)
- Explore the [Supabase Cache Helpers](https://github.com/psteinroe/supabase-cache-helpers)
- See the [Next.js Subscription Payments Starter](https://github.com/vercel/nextjs-subscription-payments) template on GitHub


## Links discovered
- [Supabase User Management example](https://github.com/supabase/supabase/blob/master/docs/img/user-management-demo.png)
- [full example on GitHub](https://github.com/supabase/supabase/tree/master/examples/user-management/nextjs-user-management)
- [`create-next-app`](https://nextjs.org/docs/getting-started)
- [supabase-js](https://github.com/supabase/supabase-js)
- [in the example repository](https://raw.githubusercontent.com/supabase/supabase/master/examples/user-management/nextjs-user-management/app/globals.css)
- [Next.js Server-Side Auth guide](https://github.com/supabase/supabase/blob/master/docs/guides/auth/server-side/nextjs.md)
- [Proxy](https://nextjs.org/docs/app/getting-started/proxy)
- [the Next.js matcher documentation](https://nextjs.org/docs/app/api-reference/file-conventions/proxy#matcher)
- [opting out of data caching](https://nextjs.org/docs/app/building-your-application/data-fetching/fetching-caching-and-revalidating#opting-out-of-data-caching)
- [Auth templates](https://github.com/supabase/supabase/blob/master/dashboard/project/_/auth/templates.md)
- [settings of your project](https://github.com/supabase/supabase/blob/master/dashboard/project/_/auth/templates.md)
- [localhost:3000/login](http://localhost:3000/login)
- [Storage](https://github.com/supabase/supabase/blob/master/docs/guides/storage.md)
- [example on GitHub](https://github.com/supabase/supabase/tree/master/examples/user-management/nextjs-user-management)
- [Build a Twitter Clone with the Next.js App Router and Supabase - free egghead course](https://egghead.io/courses/build-a-twitter-clone-with-the-next-js-app-router-and-supabase-19bebadb)
- [pre-built Auth components](https://github.com/supabase/supabase/blob/master/ui/docs/nextjs/password-based-auth.md)
- [Supabase Cache Helpers](https://github.com/psteinroe/supabase-cache-helpers)
- [Next.js Subscription Payments Starter](https://github.com/vercel/nextjs-subscription-payments)

--- apps/docs/content/guides/getting-started/tutorials/with-nuxt-3.mdx ---
---
title: 'Build a User Management App with Nuxt 3'
description: 'Learn how to use Supabase in your Nuxt 3 App.'
---

<$Partial path="uiLibCta.mdx" />
<$Partial path="quickstart_intro.mdx" />

![Supabase User Management example](/docs/img/user-management-demo.png)

<Admonition type="note">

If you get stuck while working through this guide, refer to the [full example on GitHub](https://github.com/supabase/supabase/tree/master/examples/user-management/nuxt3-user-management).

</Admonition>

<$Partial path="project_setup.mdx" variables={{ "framework": "nuxt", "tab": "frameworks" }} />

## Building the app

Let's start building the Vue 3 app from scratch.

### Initialize a Nuxt 3 app

We can use [`nuxi init`](https://nuxt.com/docs/getting-started/installation) to create an app called `nuxt-user-management`:

```bash
npx nuxi init nuxt-user-management

cd nuxt-user-management
```

Then let's install the only additional dependency: [Nuxt Supabase](https://supabase.nuxtjs.org/). We only need to import Nuxt Supabase as a dev dependency.

```bash
npm install @nuxtjs/supabase --save-dev
```

And finally we want to save the environment variables in a `.env`.
All we need are the API URL and the key that you copied [earlier](#get-api-details).

<$CodeTabs>

```bash name=.env
SUPABASE_URL="YOUR_SUPABASE_URL"
SUPABASE_KEY="YOUR_SUPABASE_PUBLISHABLE_KEY"
```

</$CodeTabs>

These variables will be exposed on the browser, and that's completely fine since we have [Row Level Security](/docs/guides/auth#row-level-security) enabled on our Database.
Amazing thing about [Nuxt Supabase](https://supabase.nuxtjs.org/) is that setting environment variables is all we need to do in order to start using Supabase.
No need to initialize Supabase. The library will take care of it automatically.

### App styling (optional)

An optional step is to update the CSS file `assets/main.css` to make the app look nice.
You can find the full contents of this file [here](https://github.com/supabase-community/nuxt3-quickstarter/blob/main/assets/main.css).

<$CodeTabs>

```typescript name=nuxt.config.ts
import { defineNuxtConfig } from 'nuxt'

// https://v3.nuxtjs.org/api/configuration/nuxt.config
export default defineNuxtConfig({
  modules: ['@nuxtjs/supabase'],
  css: ['@/assets/main.css'],
})
```

</$CodeTabs>

### Set up Auth component

Let's set up a Vue component to manage logins and sign ups. We'll use Magic Links, so users can sign in with their email without using passwords.

<$CodeTabs>

```vue name=/components/Auth.vue
<script setup>
const supabase = useSupabaseClient()

const loading = ref(false)
const email = ref('')

const handleLogin = async () => {
  try {
    loading.value = true
    const { error } = await supabase.auth.signInWithOtp({ email: email.value })
    if (error) throw error
    alert('Check your email for the login link!')
  } catch (error) {
    alert(error.error_description || error.message)
  } finally {
    loading.value = false
  }
}
</script>

<template>
  <form class="row flex-center flex" @submit.prevent="handleLogin">
    <div class="col-6 form-widget">
      <h1 class="header">Supabase + Nuxt 3</h1>
      <p class="description">Sign in via magic link with your email below</p>
      <div>
        <input class="inputField" type="email" placeholder="Your email" v-model="email" />
      </div>
      <div>
        <input
          type="submit"
          class="button block"
          :value="loading ? 'Loading' : 'Send magic link'"
          :disabled="loading"
        />
      </div>
    </div>
  </form>
</template>
```

</$CodeTabs>

### User state

To access the user information, use the composable [`useSupabaseUser`](https://supabase.nuxtjs.org/composables/usesupabaseuser) provided by the Supabase Nuxt module.

### Account component

After a user is signed in we can allow them to edit their profile details and manage their account.
Let's create a new component for that called `Account.vue`.

<$CodeTabs>

```vue name=components/Account.vue
<script setup>
const supabase = useSupabaseClient()

const loading = ref(true)
const username = ref('')
const website = ref('')
const avatar_path = ref('')

loading.value = true
const user = useSupabaseUser()

const { data } = await supabase
  .from('profiles')
  .select(`username, website, avatar_url`)
  .eq('id', user.value.id)
  .single()

if (data) {
  username.value = data.username
  website.value = data.website
  avatar_path.value = data.avatar_url
}

loading.value = false

async function updateProfile() {
  try {
    loading.value = true
    const user = useSupabaseUser()

    const updates = {
      id: user.value.id,
      username: username.value,
      website: website.value,
      avatar_url: avatar_path.value,
      updated_at: new Date(),
    }

    const { error } = await supabase.from('profiles').upsert(updates, {
      returning: 'minimal', // Don't return the value after inserting
    })
    if (error) throw error
  } catch (error) {
    alert(error.message)
  } finally {
    loading.value = false
  }
}

async function signOut() {
  try {
    loading.value = true
    const { error } = await supabase.auth.signOut()
    if (error) throw error
    user.value = null
  } catch (error) {
    alert(error.message)
  } finally {
    loading.value = false
  }
}
</script>

<template>
  <form class="form-widget" @submit.prevent="updateProfile">
    <div>
      <label for="email">Email</label>
      <input id="email" type="text" :value="user.email" disabled />
    </div>
    <div>
      <label for="username">Username</label>
      <input id="username" type="text" v-model="username" />
    </div>
    <div>
      <label for="website">Website</label>
      <input id="website" type="url" v-model="website" />
    </div>

    <div>
      <input
        type="submit"
        class="button primary block"
        :value="loading ? 'Loading ...' : 'Update'"
        :disabled="loading"
      />
    </div>

    <div>
      <button class="button block" @click="signOut" :disabled="loading">Sign Out</button>
    </div>
  </form>
</template>
```

</$CodeTabs>

### Launch!

Now that we have all the components in place, let's update `app.vue`:

<$CodeTabs>

```vue name=app.vue
<script setup>
const user = useSupabaseUser()
</script>

<template>
  <div class="container" style="padding: 50px 0 100px 0">
    <Account v-if="user" />
    <Auth v-else />
  </div>
</template>
```

</$CodeTabs>

Once that's done, run this in a terminal window:

```bash
npm run dev
```

And then open the browser to [localhost:3000](http://localhost:3000) and you should see the completed app.

![Supabase Nuxt 3](/docs/img/supabase-vue-3-demo.png)

## Bonus: Profile photos

Every Supabase project is configured with [Storage](/docs/guides/storage) for managing large files like photos and videos.

### Create an upload widget

Let's create an avatar for the user so that they can upload a profile photo. We can start by creating a new component:

<$CodeTabs>

```vue name=components/Avatar.vue
<script setup>
const props = defineProps(['path'])
const { path } = toRefs(props)

const emit = defineEmits(['update:path', 'upload'])

const supabase = useSupabaseClient()

const uploading = ref(false)
const src = ref('')
const files = ref()

const downloadImage = async () => {
  try {
    const { data, error } = await supabase.storage.from('avatars').download(path.value)
    if (error) throw error
    src.value = URL.createObjectURL(data)
  } catch (error) {
    console.error('Error downloading image: ', error.message)
  }
}

const uploadAvatar = async (evt) => {
  files.value = evt.target.files
  try {
    uploading.value = true

    if (!files.value || files.value.length === 0) {
      throw new Error('You must select an image to upload.')
    }

    const file = files.value[0]
    const fileExt = file.name.split('.').pop()
    const fileName = `${Math.random()}.${fileExt}`
    const filePath = `${fileName}`

    const { error: uploadError } = await supabase.storage.from('avatars').upload(filePath, file)

    if (uploadError) throw uploadError

    emit('update:path', filePath)
    emit('upload')
  } catch (error) {
    alert(error.message)
  } finally {
    uploading.value = false
  }
}

downloadImage()

watch(path, () => {
  if (path.value) {
    downloadImage()
  }
})
</script>

<template>
  <div>
    <img
      v-if="src"
      :src="src"
      alt="Avatar"
      class="avatar image"
      style="width: 10em; height: 10em;"
    />
    <div v-else class="avatar no-image" :style="{ height: size, width: size }" />

    <div style="width: 10em; position: relative;">
      <label class="button primary block" for="single">
        {{ uploading ? 'Uploading ...' : 'Upload' }}
      </label>
      <input
        style="position: absolute; visibility: hidden;"
        type="file"
        id="single"
        accept="image/*"
        @change="uploadAvatar"
        :disabled="uploading"
      />
    </div>
  </div>
</template>
```

</$CodeTabs>

### Add the new widget

And then we can add the widget to the Account page:

<$CodeTabs>

```vue name=components/Account.vue
<script setup>
const supabase = useSupabaseClient()

const loading = ref(true)
const username = ref('')
const website = ref('')
const avatar_path = ref('')

loading.value = true
const user = useSupabaseUser()

const { data } = await supabase
  .from('profiles')
  .select(`username, website, avatar_url`)
  .eq('id', user.value.id)
  .single()

if (data) {
  username.value = data.username
  website.value = data.website
  avatar_path.value = data.avatar_url
}

loading.value = false

async function updateProfile() {
  try {
    loading.value = true
    const user = useSupabaseUser()

    const updates = {
      id: user.value.id,
      username: username.value,
      website: website.value,
      avatar_url: avatar_path.value,
      updated_at: new Date(),
    }

    const { error } = await supabase.from('profiles').upsert(updates, {
      returning: 'minimal', // Don't return the value after inserting
    })

    if (error) throw error
  } catch (error) {
    alert(error.message)
  } finally {
    loading.value = false
  }
}

async function signOut() {
  try {
    loading.value = true
    const { error } = await supabase.auth.signOut()
    if (error) throw error
  } catch (error) {
    alert(error.message)
  } finally {
    loading.value = false
  }
}
</script>

<template>
  <form class="form-widget" @submit.prevent="updateProfile">
    <Avatar v-model:path="avatar_path" @upload="updateProfile" />
    <div>
      <label for="email">Email</label>
      <input id="email" type="text" :value="user.email" disabled />
    </div>
    <div>
      <label for="username">Name</label>
      <input id="username" type="text" v-model="username" />
    </div>
    <div>
      <label for="website">Website</label>
      <input id="website" type="url" v-model="website" />
    </div>

    <div>
      <input
        type="submit"
        class="button primary block"
        :value="loading ? 'Loading ...' : 'Update'"
        :disabled="loading"
      />
    </div>

    <div>
      <button class="button block" @click="signOut" :disabled="loading">Sign Out</button>
    </div>
  </form>
</template>
```

</$CodeTabs>

That is it! You should now be able to upload a profile photo to Supabase Storage and you have a fully functional application.


## Links discovered
- [Supabase User Management example](https://github.com/supabase/supabase/blob/master/docs/img/user-management-demo.png)
- [full example on GitHub](https://github.com/supabase/supabase/tree/master/examples/user-management/nuxt3-user-management)
- [`nuxi init`](https://nuxt.com/docs/getting-started/installation)
- [Nuxt Supabase](https://supabase.nuxtjs.org/)
- [Row Level Security](https://github.com/supabase/supabase/blob/master/docs/guides/auth#row-level-security.md)
- [here](https://github.com/supabase-community/nuxt3-quickstarter/blob/main/assets/main.css)
- [`useSupabaseUser`](https://supabase.nuxtjs.org/composables/usesupabaseuser)
- [localhost:3000](http://localhost:3000)
- [Supabase Nuxt 3](https://github.com/supabase/supabase/blob/master/docs/img/supabase-vue-3-demo.png)
- [Storage](https://github.com/supabase/supabase/blob/master/docs/guides/storage.md)

--- apps/docs/content/guides/getting-started/tutorials/with-react.mdx ---
---
title: 'Build a User Management App with React'
description: 'Learn how to use Supabase in your React App.'
---

<$Partial path="uiLibCta.mdx" />
<$Partial path="quickstart_intro.mdx" />

![Supabase User Management example](/docs/img/user-management-demo.png)

<Admonition type="note">

If you get stuck while working through this guide, refer to the [full example on GitHub](https://github.com/supabase/supabase/tree/master/examples/user-management/react-user-management).

</Admonition>

<$Partial path="project_setup.mdx" variables={{ "framework": "react", "tab": "frameworks" }} />

## Building the app

Let's start building the React app from scratch.

### Initialize a React app

We can use [Vite](https://vitejs.dev/guide/) to initialize
an app called `supabase-react`:

```bash
npm create vite@latest supabase-react -- --template react
cd supabase-react
```

Then let's install the only additional dependency: [supabase-js](https://github.com/supabase/supabase-js).

```bash
npm install @supabase/supabase-js
```

And finally, save the environment variables in a `.env.local` file.
All we need are the Project URL and the key that you copied [earlier](#get-api-details).

<$CodeTabs>

```bash name=.env
VITE_SUPABASE_URL=YOUR_SUPABASE_URL
VITE_SUPABASE_PUBLISHABLE_KEY=YOUR_SUPABASE_PUBLISHABLE_KEY
```

</$CodeTabs>

Now that we have the API credentials in place, let's create a helper file to initialize the Supabase client. These variables will be exposed
on the browser, and that's completely fine since we have [Row Level Security](/docs/guides/auth#row-level-security) enabled on our Database.

Create and edit `src/supabaseClient.js`:

<$CodeTabs>

```js name=src/supabaseClient.js
import { createClient } from '@supabase/supabase-js'

const supabaseUrl = import.meta.env.VITE_SUPABASE_URL
const supabasePublishableKey = import.meta.env.VITE_SUPABASE_PUBLISHABLE_KEY

export const supabase = createClient(supabaseUrl, supabasePublishableKey)
```

</$CodeTabs>

### App styling (optional)

An optional step is to update the CSS file `src/index.css` to make the app look nice.
You can find the full contents of this file [here](https://raw.githubusercontent.com/supabase/supabase/master/examples/user-management/react-user-management/src/index.css).

### Set up a login component

Let's set up a React component to manage logins and sign ups. We'll use Magic Links, so users can sign in with their email without using passwords.

Create and edit `src/Auth.jsx`:

<$CodeTabs>

```jsx name=src/Auth.jsx
import { useState } from 'react'
import { supabase } from './supabaseClient'

export default function Auth() {
  const [loading, setLoading] = useState(false)
  const [email, setEmail] = useState('')

  const handleLogin = async (event) => {
    event.preventDefault()

    setLoading(true)
    const { error } = await supabase.auth.signInWithOtp({ email })

    if (error) {
      alert(error.error_description || error.message)
    } else {
      alert('Check your email for the login link!')
    }
    setLoading(false)
  }

  return (
    <div className="row flex flex-center">
      <div className="col-6 form-widget">
        <h1 className="header">Supabase + React</h1>
        <p className="description">Sign in via magic link with your email below</p>
        <form className="form-widget" onSubmit={handleLogin}>
          <div>
            <input
              className="inputField"
              type="email"
              placeholder="Your email"
              value={email}
              required={true}
              onChange={(e) => setEmail(e.target.value)}
            />
          </div>
          <div>
            <button className={'button block'} disabled={loading}>
              {loading ? <span>Loading</span> : <span>Send magic link</span>}
            </button>
          </div>
        </form>
      </div>
    </div>
  )
}
```

</$CodeTabs>

### Account page

After a user is signed in we can allow them to edit their profile details and manage their account.

Let's create a new component for that called `src/Account.jsx`.

<$CodeTabs>

```jsx name=src/Account.jsx
import { useState, useEffect } from 'react'
import { supabase } from './supabaseClient'

export default function Account({ session }) {
  const [loading, setLoading] = useState(true)
  const [username, setUsername] = useState(null)
  const [website, setWebsite] = useState(null)
  const [avatar_url, setAvatarUrl] = useState(null)

  useEffect(() => {
    let ignore = false
    async function getProfile() {
      setLoading(true)
      const { user } = session

      const { data, error } = await supabase
        .from('profiles')
        .select(`username, website, avatar_url`)
        .eq('id', user.id)
        .single()

      if (!ignore) {
        if (error) {
          console.warn(error)
        } else if (data) {
          setUsername(data.username)
          setWebsite(data.website)
          setAvatarUrl(data.avatar_url)
        }
      }

      setLoading(false)
    }

    getProfile()

    return () => {
      ignore = true
    }
  }, [session])

  async function updateProfile(event, avatarUrl) {
    event.preventDefault()

    setLoading(true)
    const { user } = session

    const updates = {
      id: user.id,
      username,
      website,
      avatar_url: avatarUrl,
      updated_at: new Date(),
    }

    const { error } = await supabase.from('profiles').upsert(updates)

    if (error) {
      alert(error.message)
    } else {
      setAvatarUrl(avatarUrl)
    }
    setLoading(false)
  }

  return (
    <form onSubmit={updateProfile} className="form-widget">
      <div>
        <label htmlFor="email">Email</label>
        <input id="email" type="text" value={session.user.email} disabled />
      </div>
      <div>
        <label htmlFor="username">Name</label>
        <input
          id="username"
          type="text"
          required
          value={username || ''}
          onChange={(e) => setUsername(e.target.value)}
        />
      </div>
      <div>
        <label htmlFor="website">Website</label>
        <input
          id="website"
          type="url"
          value={website || ''}
          onChange={(e) => setWebsite(e.target.value)}
        />
      </div>

      <div>
        <button className="button block primary" type="submit" disabled={loading}>
          {loading ? 'Loading ...' : 'Update'}
        </button>
      </div>

      <div>
        <button className="button block" type="button" onClick={() => supabase.auth.signOut()}>
          Sign Out
        </button>
      </div>
    </form>
  )
}
```

</$CodeTabs>

### Launch!

Now that we have all the components in place, let's update `src/App.jsx`:

<$CodeTabs>

```jsx name=src/App.jsx
import './App.css'
import { useState, useEffect } from 'react'
import { supabase } from './supabaseClient'
import Auth from './Auth'
import Account from './Account'

function App() {
  const [session, setSession] = useState(null)

  useEffect(() => {
    supabase.auth.getSession().then(({ data: { session } }) => {
      setSession(session)
    })

    supabase.auth.onAuthStateChange((_event, session) => {
      setSession(session)
    })
  }, [])

  return (
    <div className="container" style={{ padding: '50px 0 100px 0' }}>
      {!session ? <Auth /> : <Account key={session.user.id} session={session} />}
    </div>
  )
}

export default App
```

</$CodeTabs>

Once that's done, run this in a terminal window:

```bash
npm run dev
```

And then open the browser to [localhost:5173](http://localhost:5173) and you should see the completed app.

![Supabase React](/docs/img/supabase-react-demo.png)

## Bonus: Profile photos

Every Supabase project is configured with [Storage](/docs/guides/storage) for managing large files like photos and videos.

### Create an upload widget

Let's create an avatar for the user so that they can upload a profile photo. We can start by creating a new component:

Create and edit `src/Avatar.jsx`:

<$CodeTabs>

```jsx name=src/Avatar.jsx
import { useEffect, useState } from 'react'
import { supabase } from './supabaseClient'

export default function Avatar({ url, size, onUpload }) {
  const [avatarUrl, setAvatarUrl] = useState(null)
  const [uploading, setUploading] = useState(false)

  useEffect(() => {
    if (url) downloadImage(url)
  }, [url])

  async function downloadImage(path) {
    try {
      const { data, error } = await supabase.storage.from('avatars').download(path)
      if (error) {
        throw error
      }
      const url = URL.createObjectURL(data)
      setAvatarUrl(url)
    } catch (error) {
      console.log('Error downloading image: ', error.message)
    }
  }

  async function uploadAvatar(event) {
    try {
      setUploading(true)

      if (!event.target.files || event.target.files.length === 0) {
        throw new Error('You must select an image to upload.')
      }

      const file = event.target.files[0]
      const fileExt = file.name.split('.').pop()
      const fileName = `${Math.random()}.${fileExt}`
      const filePath = `${fileName}`

      const { error: uploadError } = await supabase.storage.from('avatars').upload(filePath, file)

      if (uploadError) {
        throw uploadError
      }

      onUpload(event, filePath)
    } catch (error) {
      alert(error.message)
    } finally {
      setUploading(false)
    }
  }

  return (
    <div>
      {avatarUrl ? (
        <img
          src={avatarUrl}
          alt="Avatar"
          className="avatar image"
          style={{ height: size, width: size }}
        />
      ) : (
        <div className="avatar no-image" style={{ height: size, width: size }} />
      )}
      <div style={{ width: size }}>
        <label className="button primary block" htmlFor="single">
          {uploading ? 'Uploading ...' : 'Upload'}
        </label>
        <input
          style={{
            visibility: 'hidden',
            position: 'absolute',
          }}
          type="file"
          id="single"
          accept="image/*"
          onChange={uploadAvatar}
          disabled={uploading}
        />
      </div>
    </div>
  )
}
```

</$CodeTabs>

### Add the new widget

And then we can add the widget to the Account page at `src/Account.jsx`:

<$CodeTabs>

```jsx name=src/Account.jsx
// Import the new component
import Avatar from './Avatar'

// ...

return (
  <form onSubmit={updateProfile} className="form-widget">
    {/* Add to the body */}
    <Avatar
      url={avatar_url}
      size={150}
      onUpload={(event, url) => {
        updateProfile(event, url)
      }}
    />
    {/* ... */}
  </form>
)
```

</$CodeTabs>

At this stage you have a fully functional application!


## Links discovered
- [Supabase User Management example](https://github.com/supabase/supabase/blob/master/docs/img/user-management-demo.png)
- [full example on GitHub](https://github.com/supabase/supabase/tree/master/examples/user-management/react-user-management)
- [Vite](https://vitejs.dev/guide/)
- [supabase-js](https://github.com/supabase/supabase-js)
- [Row Level Security](https://github.com/supabase/supabase/blob/master/docs/guides/auth#row-level-security.md)
- [here](https://raw.githubusercontent.com/supabase/supabase/master/examples/user-management/react-user-management/src/index.css)
- [localhost:5173](http://localhost:5173)
- [Supabase React](https://github.com/supabase/supabase/blob/master/docs/img/supabase-react-demo.png)
- [Storage](https://github.com/supabase/supabase/blob/master/docs/guides/storage.md)

--- examples/_internal/README.md ---
# Internal fixtures for examples

This directory contains some fixtures for internal testing purposes.


--- examples/archive/README.md ---
# Supabase Examples Archive

## supabase-js v1

You can find the supabase-js v1 examples at [github.com/supabase/examples-archive](https://github.com/supabase/examples-archive).


## Links discovered
- [github.com/supabase/examples-archive](https://github.com/supabase/examples-archive)

--- examples/clerk/README.md ---
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).

## Getting Started

First, run the development server:

```bash
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
```

Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.

You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.

This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.

## Learn More

To learn more about Next.js, take a look at the following resources:

- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.

You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!

## Deploy on Vercel

The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.

Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.


## Links discovered
- [Next.js](https://nextjs.org)
- [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app)
- [http://localhost:3000](http://localhost:3000)
- [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts)
- [Geist](https://vercel.com/font)
- [Next.js Documentation](https://nextjs.org/docs)
- [Learn Next.js](https://nextjs.org/learn)
- [the Next.js GitHub repository](https://github.com/vercel/next.js)
- [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme)
- [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying)

--- examples/edge-functions/README.md ---
# Supabase Edge Function Examples

## What are Supabase Edge Functions?

[Supabase Edge Functions](https://supabase.com/edge-functions) are written in TypeScript, run via Deno, and deployed with the Supabase CLI. Please [download](https://github.com/supabase/cli#install-the-cli) the latest version of the Supabase CLI, or [upgrade](https://github.com/supabase/cli#install-the-cli) it if you have it already installed.

## Example Functions

We're constantly adding new Function Examples, [check our docs](https://supabase.com/docs/guides/functions#examples) for a complete list!

## Develop locally

- Run `supabase start` (make sure your Docker daemon is running.)
- Run `cp ./supabase/.env.local.example ./supabase/.env.local` to create your local `.env` file.
- Set the required variables for the corresponding edge functions in the `.env.local` file.
- Run `supabase functions serve --env-file ./supabase/.env.local --no-verify-jwt`
- Run the CURL command in the example function, or use the [invoke method](https://supabase.com/docs/reference/javascript/invoke) on the Supabase client or use the test client [app](./app/).

## Test Client

This example includes a create-react-app in the [`./app/`](./app/) directory which you can use as a sort of postman to make test requests both locally and to your deployed functions.

### Test locally

- `cd app`
- `npm install`
- `npm start`

Note: when testing locally, the select dropdown doesn't have any effect, and invoke simply calls whatever function is currently served by the CLI.

## Deploy

- Generate access token and log in to CLI
  - Navigate to https://supabase.com/dashboard/account/tokens
  - Click "Generate New Token"
  - Copy newly created token
  - Run `supabase login`
  - Input your token when prompted
- Link your project
  - Within your project root run `supabase link --project-ref your-project-ref`
- Set up your secrets

  - Run `supabase secrets set --env-file ./supabase/.env.local` to set the environment variables.

  (This is assuming your local and production secrets are the same. The recommended way is to create a separate `.env` file for storing production secrets, and then use it to set the environment variables while deploying.)

  - You can run `supabase secrets list` to check that it worked and also to see what other env vars are set by default.

- Deploy the function
  - Within your project root run `supabase functions deploy your-function-name`
- In your [`./app/.env`](./app/.env) file remove the `SUPA_FUNCTION_LOCALHOST` variable and restart your Expo app.

### Test deployed functions

This example includes a create-react-app in the [`./app/`](./app/) directory which you can use as a sort of postman to make test requests both locally and to your deployed functions.

- `cd app`
- `cp .env.example .env`
- Fill in your env vars from https://supabase.com/dashboard/project/_/settings/api
- `npm install`
- `npm start`

### Deploy via GitHub Actions

This example includes a [deploy GitHub Action](./.github/workflows/deploy.yaml) that automatically deploys your Supabase Edge Functions when pushing to or merging into the main branch.

You can use the [`setup-cli` GitHub Action](https://github.com/marketplace/actions/supabase-cli-action) to run Supabase CLI commands in your GitHub Actions, for example to deploy a Supabase Edge Function:

```yaml
name: Deploy Function

on:
  push:
    branches:
      - main
  workflow_dispatch:

jobs:
  deploy:
    runs-on: ubuntu-latest

    env:
      SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }}
      PROJECT_ID: your-project-id

    steps:
      - uses: actions/checkout@v3

      - uses: supabase/setup-cli@v1
        with:
          version: latest

      - 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
```

## 👁⚡️👁

\o/ That's it, you can now invoke your Supabase Function via the [`supabase-js`](https://supabase.com/docs/reference/javascript/invoke) and [`supabase-dart`](https://supabase.com/docs/reference/dart/invoke) client libraries. (More client libraries coming soon. Check the [supabase-community](https://github.com/supabase-community#client-libraries) org for details).

For more info on Supabase Functions, check out the [docs](https://supabase.com/docs/guides/functions) and the [examples](https://github.com/supabase/supabase/tree/master/examples/edge-functions).


## Links discovered
- [Supabase Edge Functions](https://supabase.com/edge-functions)
- [download](https://github.com/supabase/cli#install-the-cli)
- [upgrade](https://github.com/supabase/cli#install-the-cli)
- [check our docs](https://supabase.com/docs/guides/functions#examples)
- [invoke method](https://supabase.com/docs/reference/javascript/invoke)
- [app](https://github.com/supabase/supabase/blob/master/examples/edge-functions/app.md)
- [`./app/`](https://github.com/supabase/supabase/blob/master/examples/edge-functions/app.md)
- [`./app/.env`](https://github.com/supabase/supabase/blob/master/examples/edge-functions/app/.env)
- [deploy GitHub Action](https://github.com/supabase/supabase/blob/master/examples/edge-functions/.github/workflows/deploy.yaml)
- [`setup-cli` GitHub Action](https://github.com/marketplace/actions/supabase-cli-action)
- [v1.62.0](https://github.com/supabase/cli/releases/tag/v1.62.0)
- [JWT verification](https://github.com/supabase/supabase/blob/master/docs/reference/cli/config#functions.function_name.verify_jwt)
- [import map location](https://github.com/supabase/supabase/blob/master/docs/reference/cli/config#functions.function_name.import_map)
- [`supabase-js`](https://supabase.com/docs/reference/javascript/invoke)
- [`supabase-dart`](https://supabase.com/docs/reference/dart/invoke)
- [supabase-community](https://github.com/supabase-community#client-libraries)
- [docs](https://supabase.com/docs/guides/functions)
- [examples](https://github.com/supabase/supabase/tree/master/examples/edge-functions)

--- examples/oauth-app-authorization-flow/README.md ---
# Supabase OAuth Apps Login Flow

1. Create OAuth App at https://supabase.com/dashboard/org/_/apps
2. Use http://localhost:3000 as `Authorization callback URLs`
3. Copy `.env.example` to `.env` and fill `Client ID` and `Client Secret` with values from newly created app
4. `bun install`
5. `bun run dev`
6. Open http://localhost:3000


--- examples/with-cloudflare-workers/README.md ---
# Query Supabase from Cloudflare Worker

**[📹 Video](https://egghead.io/lessons/cloudflare-query-supabase-from-cloudflare-worker?af=9qsk0a)**

Supabase JS is an NPM package which provides a simple interface from JavaScript to our Supabase project. It allows us to query and mutate data using its Object Relational Mapping (ORM) syntax, and subscribe to realtime events.

In this video, we install the Supabase JS package and create a new client using our project's URL and Anon Key. These can be found in the Supabase dashboard for our project, under `Settings > API`.

We store these values as secrets in our Cloudflare account, and use them to instantiate a new Supabase client.

Additionally, we write a query to select all of our articles from our Supabase instance, and send them back as the response from our Cloudflare Worker.

In order to send a JSON response, we first stringify the object we get back from Supabase, and then set a `Content-Type` header to notify the browser that this will be a type of `application/json`.

## Code Snippets

**Install Supabase JS**

```bash
npm i @supabase/supabase-js
```

**Create a Cloudflare secret**

```bash
npx wrangler secret put NAME
```

**Add a secret for SUPABASE_URL**

```bash
npx wrangler secret put SUPABASE_URL
```

**Run wrangler development server**

```bash
npx wrangler dev
```

**Add a secret for SUPABASE_ANON_KEY**

```bash
npx wrangler secret put SUPABASE_ANON_KEY
```

**Query data from Supabase**

```javascript
const { data } = await supabase.from("articles").select("*");
```

**Send JSON response**

```javascript
return new Response(JSON.stringify(data), {
  headers: {
    "Content-Type": "application/json",
  },
});
```

## Resources

- [Selecting data with Supabase JS](https://supabase.com/docs/reference/javascript/select)
- [Introducing Secrets and Environment Variables to Cloudflare Workers](https://blog.cloudflare.com/workers-secrets-environment/)
- [Cloudflare docs for sending JSON responses](https://developers.cloudflare.com/workers/examples/return-json/)

---

[👉 Next lesson](https://github.com/dijonmusters/supabase-data-at-the-edge/tree/main/04-proxy-supabase-requests-with-cloudflare-workers-and-itty-router)

---

Enjoying the course? Follow Jon Meyers on [Twitter](https://twitter.com/jonmeyers_io) and subscribe to the [YouTube channel](https://www.youtube.com/c/jonmeyers).


## Links discovered
- [📹 Video](https://egghead.io/lessons/cloudflare-query-supabase-from-cloudflare-worker?af=9qsk0a)
- [Selecting data with Supabase JS](https://supabase.com/docs/reference/javascript/select)
- [Introducing Secrets and Environment Variables to Cloudflare Workers](https://blog.cloudflare.com/workers-secrets-environment/)
- [Cloudflare docs for sending JSON responses](https://developers.cloudflare.com/workers/examples/return-json/)
- [👉 Next lesson](https://github.com/dijonmusters/supabase-data-at-the-edge/tree/main/04-proxy-supabase-requests-with-cloudflare-workers-and-itty-router)
- [Twitter](https://twitter.com/jonmeyers_io)
- [YouTube channel](https://www.youtube.com/c/jonmeyers)

--- examples/ai/aws_bedrock_image_search/README.md ---
# Image Search with Amazon Bedrock and Supabase Vector

In this example we're implementing image search using the [Amazon Titan Multimodal Embeddings G1](https://aws.amazon.com/bedrock/titan), a set of pre-trained high-performing image, multimodal, and text model, accessible via a fully managed API.

We're implementing two methods in the [`/image_search/main.py` file](/image_search/main.py):

1. The `seed` method generates embeddings for the images in the `images` folder and upserts them into a collection in Supabase Vector.
2. The `search` method generates an embedding from the search query and performs a vector similarity search query.

## Setup

- Install poetry: `pip install poetry`
- Activate the virtual environment: `poetry shell`
  - (to leave the venv just run `exit`)
- Install app dependencies: `poetry install`

## Run locally

### Generate the embeddings and seed the collection

- `supabase start`
- `poetry run seed`
- Check the embeddings stored in the local Supabase Dashboard: http://localhost:54323/project/default/editor > schema: vecs

### Perform a search

- `poetry run search "bike in front of red brick wall"`

## Run on hosted Supabase project

- Set `DB_CONNECTION` with the connection string from your hosted Supabase Dashboard: https://supabase.com/dashboard/project/_/database/settings > Connection string > URI

## Attributions

### Models

[Amazon Titan Multimodal Embeddings G1](https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-titan-embed-mm.html)

### Images

Images from https://unsplash.com/license via https://picsum.photos/


## Links discovered
- [Amazon Titan Multimodal Embeddings G1](https://aws.amazon.com/bedrock/titan)
- [`/image_search/main.py` file](https://github.com/supabase/supabase/blob/master/image_search/main.py)
- [Amazon Titan Multimodal Embeddings G1](https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-titan-embed-mm.html)

--- examples/ai/edge-functions/README.md ---
# AI Inference in Supabase Edge Functions

Since Supabase Edge Runtime [v1.36.0](https://github.com/supabase/edge-runtime/releases/tag/v1.36.0) you can run the [`gte-small` model](https://huggingface.co/Supabase/gte-small) natively within Supabase Edge Functions without any external dependencies! This allows you to easily generate text embeddings without calling any external APIs!

## Semantic Search with pgvector and Supabase Edge Functions

This demo consists of three parts:

1. A [`generate-embedding`](./supabase/functions/generate-embedding/index.ts) database webhook edge function which generates embeddings when a content row is added (or updated) in the [`public.embeddings`](./supabase/migrations/20240408072601_embeddings.sql) table.
2. A [`query_embeddings` Postgres function](./supabase/migrations/20240410031515_vector-search.sql) which allows us to perform similarity search from an egde function via [Remote Procedure Call (RPC)](https://supabase.com/docs/guides/database/functions?language=js).
3. A [`search` edge function](./supabase/functions/search/index.ts) which generates the embedding for the search term, performs the similarity search via RPC function call, and returns the result.

## Deploy

- Link your project: `supabase link`
- Deploy Edge Functions: `supabase functions deploy`
- Update project config to [enable webhooks](https://supabase.com/docs/guides/local-development/cli/config#experimental.webhooks.enabled): `supabase config push`
- Navigate to the [database-webhook](./supabase/migrations/20240410041607_database-webhook.sql) migration file and insert your `generate-embedding` function details.
- Push up the database schema `supabase db push`

## Run

Run a search via curl POST request:

```bash
curl -i --location --request POST 'https://<PROJECT-REF>.supabase.co/functions/v1/search' \
    --header 'Authorization: Bearer <SUPABASE_ANON_KEY>' \
    --header 'Content-Type: application/json' \
    --data '{"search":"vehicles"}'
```


## Links discovered
- [v1.36.0](https://github.com/supabase/edge-runtime/releases/tag/v1.36.0)
- [`gte-small` model](https://huggingface.co/Supabase/gte-small)
- [`generate-embedding`](https://github.com/supabase/supabase/blob/master/examples/ai/edge-functions/supabase/functions/generate-embedding/index.ts)
- [`public.embeddings`](https://github.com/supabase/supabase/blob/master/examples/ai/edge-functions/supabase/migrations/20240408072601_embeddings.sql)
- [`query_embeddings` Postgres function](https://github.com/supabase/supabase/blob/master/examples/ai/edge-functions/supabase/migrations/20240410031515_vector-search.sql)
- [Remote Procedure Call (RPC)](https://supabase.com/docs/guides/database/functions?language=js)
- [`search` edge function](https://github.com/supabase/supabase/blob/master/examples/ai/edge-functions/supabase/functions/search/index.ts)
- [enable webhooks](https://supabase.com/docs/guides/local-development/cli/config#experimental.webhooks.enabled)
- [database-webhook](https://github.com/supabase/supabase/blob/master/examples/ai/edge-functions/supabase/migrations/20240410041607_database-webhook.sql)

--- examples/ai/image_search/README.md ---
# Image Search with Supabase Vector

In this example we're implementing image search using the [OpenAI CLIP Model](https://github.com/openai/CLIP), which was trained on a variety of (image, text)-pairs.

We're implementing two methods in the [`/image_search/main.py` file](/image_search/main.py):

1. The `seed` method generates embeddings for the images in the `images` folder and upserts them into a collection in Supabase Vector.
2. The `search` method generates an embedding from the search query and performs a vector similarity search query.

## Prerequisites

Before running this example, ensure you have:

- Python 3.8 or higher installed
- A Supabase account (sign up at https://supabase.com)
- Poetry package manager
- Basic familiarity with vector databases (helpful but not required)

## Setup

- Create a new project in your [Supabase dashboard](https://supabase.com/dashboard)
- Go to Settings > Database and copy your connection string
- Ensure the Vector extension is enabled in your project
- Install poetry: `pip install poetry`
- Activate the virtual environment: `poetry shell`
  - (to leave the venv just run `exit`)
- Install app dependencies: `poetry install`

## Run locally

### Generate the embeddings and seed the collection

- `supabase start`
- `poetry run seed`
- Check the embeddings stored in the local Supabase Dashboard: http://localhost:54323/project/default/editor > schema: vecs

**What to expect:** The seed command will process all images in the `images` folder and generate vector embeddings for each one.

### Perform a search

- `poetry run search "bike in front of red brick wall"`

**What to expect:** The search will return a list of images ranked by similarity to your search query, along with similarity scores.

## Run on hosted Supabase project

- Set `DB_CONNECTION` with the connection string from your hosted Supabase Dashboard: https://supabase.com/dashboard/project/_/database/settings > Connection string > URI

## Example Search Queries

Try these search queries to test the image search functionality:

- `"bike in front of red brick wall"`
- `"person walking in park"`
- `"blue sky with clouds"`
- `"city street at night"`

## Troubleshooting

**Common Issues:**

- **Poetry not found:** Make sure Poetry is installed with `pip install poetry`
- **Connection errors:** Verify your Supabase connection string is correct
- **No search results:** Ensure you've run the seed command first to populate the database
- **Python version errors:** This example requires Python 3.8 or higher

## How It Works

This example uses the CLIP (Contrastive Language-Image Pre-training) model to:

1. Convert images into high-dimensional vector representations (embeddings)
2. Convert text search queries into similar vector representations
3. Find images with embeddings most similar to the search query embedding
4. Return ranked results based on vector similarity scores

## Attributions

### Models

[clip-ViT-B-32](https://www.sbert.net/examples/applications/image-search/README.html) via [Hugging Face](https://huggingface.co/sentence-transformers/clip-ViT-B-32)

### Images

Images from https://unsplash.com/license via https://picsum.photos/


## Links discovered
- [OpenAI CLIP Model](https://github.com/openai/CLIP)
- [`/image_search/main.py` file](https://github.com/supabase/supabase/blob/master/image_search/main.py)
- [Supabase dashboard](https://supabase.com/dashboard)
- [clip-ViT-B-32](https://www.sbert.net/examples/applications/image-search/README.html)
- [Hugging Face](https://huggingface.co/sentence-transformers/clip-ViT-B-32)

--- examples/auth/expo-social-auth/README.md ---
# Welcome to your Expo app 👋

This is an [Expo](https://expo.dev) project created with [`create-expo-app`](https://www.npmjs.com/package/create-expo-app).

## Get started

1. Install dependencies

   ```bash
   npm install
   ```

2. Update the `.env` file with your Supabase project's URL and Anon Key.

3. Start the app

   ```bash
   npx expo start
   ```

In the output, you'll find options to open the app in a

- [development build](https://docs.expo.dev/develop/development-builds/introduction/)
- [Android emulator](https://docs.expo.dev/workflow/android-studio-emulator/)
- [iOS simulator](https://docs.expo.dev/workflow/ios-simulator/)
- [Expo Go](https://expo.dev/go), a limited sandbox for trying out app development with Expo

You can start developing by editing the files inside the **app** directory. This project uses [file-based routing](https://docs.expo.dev/router/introduction).

## Get a fresh project

When you're ready, run:

```bash
npm run reset-project
```

This command will move the starter code to the **app-example** directory and create a blank **app** directory where you can start developing.

## Learn more

To learn more about developing your project with Expo, look at the following resources:

- [Expo documentation](https://docs.expo.dev/): Learn fundamentals, or go into advanced topics with our [guides](https://docs.expo.dev/guides).
- [Learn Expo tutorial](https://docs.expo.dev/tutorial/introduction/): Follow a step-by-step tutorial where you'll create a project that runs on Android, iOS, and the web.

## Join the community

Join our community of developers creating universal apps.

- [Expo on GitHub](https://github.com/expo/expo): View our open source platform and contribute.
- [Discord community](https://chat.expo.dev): Chat with Expo users and ask questions.


## Links discovered
- [Expo](https://expo.dev)
- [`create-expo-app`](https://www.npmjs.com/package/create-expo-app)
- [development build](https://docs.expo.dev/develop/development-builds/introduction/)
- [Android emulator](https://docs.expo.dev/workflow/android-studio-emulator/)
- [iOS simulator](https://docs.expo.dev/workflow/ios-simulator/)
- [Expo Go](https://expo.dev/go)
- [file-based routing](https://docs.expo.dev/router/introduction)
- [Expo documentation](https://docs.expo.dev/)
- [guides](https://docs.expo.dev/guides)
- [Learn Expo tutorial](https://docs.expo.dev/tutorial/introduction/)
- [Expo on GitHub](https://github.com/expo/expo)
- [Discord community](https://chat.expo.dev)

--- packages/api-types/index.ts ---
export type { webhooks, $defs } from './types/api'
import type {
  paths as apiPaths,
  components as apiComponents,
  operations as apiOperations,
} from './types/api'
import type {
  paths as platformPaths,
  components as platformComponents,
  operations as platformOperations,
} from './types/platform'

export interface paths extends apiPaths, platformPaths {}
export interface operations extends apiOperations, platformOperations {}
export interface components {
  schemas: apiComponents['schemas'] & platformComponents['schemas']
  responses: apiComponents['responses'] & platformComponents['responses']
  parameters: apiComponents['parameters'] & platformComponents['parameters']
  requestBodies: apiComponents['requestBodies'] & platformComponents['requestBodies']
  headers: apiComponents['headers'] & platformComponents['headers']
  pathItems: apiComponents['pathItems'] & platformComponents['pathItems']
}


--- apps/www/_blog/2021-03-08-toad-a-link-shortener-with-simple-apis-for-low-coders.mdx ---
---
title: Toad, a link shortener with simple APIs for low-coders
description: An easy-to-use link shortening tool with simple APIs
author: rory_wilding
author_title: Supabase
author_url: https://github.com/roryw10
author_image_url: https://github.com/roryw10.png
image: toadli-og.jpg
thumb: toadli-website.jpg
categories:
  - engineering
tags:
  - supabase
date: '03-08-2021'
---

Zach Waterfield is an engineer, investor, and founder of Kopa - a short term furnished rental marketplace with operations in over 400 cities in the USA. Zach's latest project, [Toad](https://toadli.co/), is an easy-to-use link shortening tool with simple APIs, aimed to empower low-coders. Toad provides a simple dashboard, analytics, and API designed with low-coders in mind.

Learn why Zach used Supabase as part of his serverless SAAS setup.

### Toad APIs for low Coders

Zach loves link shorteners. However, for low-coders, link shorteners tend to have cumbersome APIs limiting their utility. He wanted to build a link shortening tool with simple APIs that are simple for no-coders to use.

## The power of a serverless Supa-stack

Zach started this project with a low-management overhead in mind. He needed a robust and scalable stack that doesn't need refactoring when his service user volume grows. Zach knew that a back-end as-a-service abstracts away many of the dev-ops tasks, such as provisioning server instances and setting up databases. From experience, he knew that Firebase would be too cumbersome.

Zach decided that combining Supabase, Lambda functions, Next.js, TailwindCSS, and Vercel would be the perfect Supa-stack to achieve his goal of low-management overhead. This setup removes significant dev-ops headaches with no need to think about continuous integration - when he wants to make a change, it is good to go. As a bonus, this setup is virtually free until he reaches significant user volumes.

### Build it once, use it forever

Zach was surprised how rapidly he was able to build and deploy Toadli. He now has a template project that he plans to customise for future SaaS services. Zach has a stack that is scalable, reusable, and low-management. Toad has early paying customers, and traffic volumes are increasing as low-coders become aware of how easy Toad APIs are to use.

<Quote img="zach-waterfield.png" caption="Zach Waterfield - Toad">
  Full Serverless is an amazing developer experience. You can get high-quality results fast,
  especially when Supabase abstracts away all the back-end headaches
</Quote>

## Spend more time creating and less time managing back ends

Thanks to Supabase, Zach is able to focus on the creative process and launch services like [Toad](https://toadli.co/) with all the functionality he wants to offer. He has peace of mind that his stack is low-management and will scale as his project continues to gain traction.


## Links discovered
- [Toad](https://toadli.co/)

--- apps/www/_blog/2022-08-02-supabase-flutter-sdk-1-developer-preview.mdx ---
---
title: 'Supabase Flutter SDK 1.0 Developer Preview'
description: Supabase Flutter SDK is getting a major update and we need your help making it better.
author: tyler_shukert
image: flutter-1/supabase-flutter-1.jpg
thumb: flutter-1/supabase-flutter-1.jpg
categories:
  - engineering
tags:
  - flutter
  - mobile
date: '2022-08-02'
toc_depth: 3
---

Today, we are releasing of Developer Preview version of v1.0 of [Supabase Flutter SDK](https://pub.dev/packages/supabase_flutter/versions/1.0.0-dev.1). Flutter has quickly become one of the most popular frameworks for developers to build cross-platform mobile apps. We can attest to that growth, our Flutter SDK is one of the most popular libraries and each day we see more Flutter devs choosing Supabase.

For this release, our main focus is developer experiences. We would love for you to try the SDK and provide your feedback so that we can continue to improve!

Before we dive into the actual updates, I would like to thank all the community contributors who have helped the library to be where it is today.

## Better developer experience

Until now, there were some disputable implementations in the Flutter SDK. We've made several improvements:

### Automatically handling auth state persistence

Previously, `supabase-flutter` required a class that extends `SupabaseAuthState` or `SupabaseAuthRequiredState` to persist auth state. With `supabase-flutter` 1.0, you no longer need to include these classes.

All you need to persist the auth state is initialize Supabase and everything else will be automatically taken care of. `SupabaseAuthState` and `SupabaseAuthRequiredState` have been removed from the code base.

```dart
// Before
await Supabase.initialize(
  url: 'SUPABASE_URL',
  anonKey: 'SUPABASE_ANON_KEY',
);
...

class AuthState<T extends StatefulWidget> extends SupabaseAuthState<T> {
  ...
}

// After
await Supabase.initialize(
  url: 'SUPABASE_URL',
  anonKey: 'SUPABASE_ANON_KEY',
);
```

### Automatically handling deep links

Deep link handling had similar issues previously, requiring you to implement `SupabaseAuthState` or `SupabaseAuthRequiredState` classes.

With the 1.0 update, you no longer need to use these classes, and deep links will be automatically handled. You can listen to `onAuthStateChange` to handle when a deep link is received to redirect users to a new screen.

```dart
// Before
void onReceivedAuthDeeplink(Uri uri) {
  Supabase.instance.log('onReceivedAuthDeeplink uri: $uri');
}

// After
await Supabase.instance.initialize(
  url: 'SUPABASE_URL',
  anonKey: 'SUPABASE_ANON_KEY',
);
```

### Throwing errors instead of returning them

When `supabase-dart` and `supabase-flutter` were created, we wanted to mirror the JavaScript library as much as possible. We soon realized that some syntax does not fit well when written in Dart. Throwing vs returning error is a good example of that. Since Dart does not have object destruction, the code becomes a bit tedious when errors are returned.

With `supabase-flutter` 1.0, we are throwing errors instead of returning them. This is consistent across all features from `auth`, `postgrest`, and `storage`.

```dart
// Before
final response = await Supabase.instance.from('messages').select().execute();
final data = response.data;
final error = response.error;

// After
try {
  final data = await Supabase.instance.from('messages').select();
} catch(error) {
  // Handle error here
}
```

### No more `.execute()` to get the data

We want this SDK to be as close as possible to the JavaScript SDK to provide consistent developer experience no matter what programming language you are using. Prior to the 1.0 update, whenever you called the `postgrest` endpoints, you had to call `.execute()` at the end of each query.

`.execute()` is now deprecated. You no longer needed it to query data from your Supabase database. This update, along with many many other improvements across the whole library, has been done by [Bruno D'Luka](https://github.com/bdlukaa), and I would love to give him a special shout out here!

```dart
// Before
final response = await Supabase.instance.from('messages').select().execute();
final data = response.data;

// After
final data = await Supabase.instance.from('messages').select();
```

## Desktop support for deeplinks

Ever since `supabase-flutter` was born, it supported only iOS, Android and Web for deep linking. This was a limitation of the deep link library that we were using.

With the 1.0 launch, we are moving to use [app_links](https://pub.dev/packages/app_links), which will enable us to support MacOS and Windows applications as well! Linux support is being worked on - follow the repo to keep updated.

![Supabase Flutter desktop support](/images/blog/flutter-1/supported-platforms-table.png)

## Multiplayer support

[Multiplayer](https://supabase.com/blog/supabase-realtime-with-multiplayer-features) is the next generation Supabase Realtime engine that was announced at the previous launch week.

We want our Flutter developers to experience this new multiplayer feature as well, so are working hard at bringing it to our Flutter SDK. It is not yet included in the developer preview of Supabase Flutter 1.0, but will be part of it when stable launch has been released.

## Supabase Auth UI for Flutter

![Supabase Auth UI for Flutter](/images/blog/flutter-1/supabase-flutter-auth-ui.png)

Last but not least, we are bringing you another library, the Supabase Auth UI for Supabase! When released, this library will enable you to implement a basic authentication screen without building it yourself. You can just load the library and display a nice looking Auth UI. The library takes your theme settings automatically to match the look and feel of your application.

You can get started with it on [pub.dev](https://pub.dev/packages/supabase_auth_ui).

I would like to thank [Fatuma](https://twitter.com/XquisiteDreamer) for single-handedly working on bringing us an easier authentication experience.

```dart
// Email and password signin form
SupaEmailAuth(
  authAction: AuthAction.signIn,
  redirectUrl: '/home',
),

// Magic Link signin form
SupaMagicAuth(),

// Social Login Buttons
SupaSocialsAuth(
  socialProviders: [
  SocialProviders.apple,
  SocialProviders.google,
  ],
  colored: true,
),
```

## Final thoughts

These updates are just the tip of the iceberg for 1.0. There are been many bug fixes and features constantly being added to the Supabase Flutter SDK. This could not have been possible without the help from the open source community. Here, I would also like to give a shout out to two other developers who have been a major part of the journey of this SDK: [Vinzent](https://twitter.com/Vinzent03_) and [Daniel Mossaband](https://github.com/DanMossa). They have been a huge part of the Supabase Flutter SDK - not just for the 1.0 release, but throughout the lifetime of the library.
For those of you who want to try out the new SDK, you can get the developer preview version from [supabase-flutter](https://pub.dev/packages/supabase_flutter/versions/1.0.0-dev.1) pub.dev page or can simply copy and paste the following into your pubspec.yaml file.

```yaml
supabase_flutter: ^1.0.0-dev.1
```

If you have any feedbacks, please let us know in the issues of the [supabase-flutter](https://github.com/supabase-community/supabase-flutter/issues) repository.

## Flutter Resources

- [supabase-flutter 1.0 developer preview](https://pub.dev/packages/supabase_flutter)
- [Flutter Tutorial: building a Flutter chat app](https://supabase.com/blog/flutter-tutorial-building-a-chat-app)
- [Flutter Tutorial - Part 2: Authentication and Authorization with RLS](https://supabase.com/blog/flutter-authentication-and-authorization-with-rls)
- [How to build a real-time multiplayer game with Flutter Flame](https://supabase.com/blog/flutter-real-time-multiplayer-game)
- [Build a Flutter app with Very Good CLI and Supabase](https://verygood.ventures/blog/flutter-app-very-good-cli-supabase)


## Links discovered
- [Supabase Flutter SDK](https://pub.dev/packages/supabase_flutter/versions/1.0.0-dev.1)
- [Bruno D'Luka](https://github.com/bdlukaa)
- [app_links](https://pub.dev/packages/app_links)
- [Supabase Flutter desktop support](https://github.com/supabase/supabase/blob/master/images/blog/flutter-1/supported-platforms-table.png)
- [Multiplayer](https://supabase.com/blog/supabase-realtime-with-multiplayer-features)
- [Supabase Auth UI for Flutter](https://github.com/supabase/supabase/blob/master/images/blog/flutter-1/supabase-flutter-auth-ui.png)
- [pub.dev](https://pub.dev/packages/supabase_auth_ui)
- [Fatuma](https://twitter.com/XquisiteDreamer)
- [Vinzent](https://twitter.com/Vinzent03_)
- [Daniel Mossaband](https://github.com/DanMossa)
- [supabase-flutter](https://pub.dev/packages/supabase_flutter/versions/1.0.0-dev.1)
- [supabase-flutter](https://github.com/supabase-community/supabase-flutter/issues)
- [supabase-flutter 1.0 developer preview](https://pub.dev/packages/supabase_flutter)
- [Flutter Tutorial: building a Flutter chat app](https://supabase.com/blog/flutter-tutorial-building-a-chat-app)
- [Flutter Tutorial - Part 2: Authentication and Authorization with RLS](https://supabase.com/blog/flutter-authentication-and-authorization-with-rls)
- [How to build a real-time multiplayer game with Flutter Flame](https://supabase.com/blog/flutter-real-time-multiplayer-game)
- [Build a Flutter app with Very Good CLI and Supabase](https://verygood.ventures/blog/flutter-app-very-good-cli-supabase)

--- apps/www/_blog/2022-08-15-supabase-cli-v1-and-admin-api-beta.mdx ---
---
title: 'Supabase CLI v1 and Management API Beta'
description: We are moving Supabase CLI v1 out of beta, and releasing Management API beta.
author: soedirgo,qiao
image: lw5-cli/thumbnail.jpg
thumb: lw5-cli/thumbnail.jpg
categories:
  - product
tags:
  - launch-week
date: '2022-08-15'
toc_depth: 3
video: https://www.youtube.com/v/OpPOaJI_Z28
---

Today we are moving the Supabase CLI v1 out of beta. The Supabase CLI is capable of managing database migrations and generating TypeScript types. Follow these [install instructions](https://supabase.com/docs/guides/cli) to get started.

In addition, we are releasing a Management API (in beta). The Management API is a REST API that allows you to manage organizations, projects, Edge Functions, and more. You can read the [API docs](https://supabase.com/docs/reference/api) or interact with the Management API from the Supabase CLI v1.

(Note: The Management API was previously called the Admin API.)

<div className="video-container">
  <iframe
    className="w-full"
    src="https://www.youtube-nocookie.com/embed/OpPOaJI_Z28"
    title="YouTube video player"
    frameborder="0"
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
    allowfullscreen
  ></iframe>
</div>

<small>
  <i>Watch the CLI team announcing the new features.</i>
</small>

## Manage organizations, projects, Edge Functions, and more

If you've used [Supabase Edge Functions](https://supabase.com/blog/supabase-edge-functions#quickstart), you've used the Supabase CLI. We're extending it to let you manage organizations and projects.

```bash
supabase login
supabase orgs list
supabase projects create my-project --org-id cool-green-pqdr0qc --db-password ******** --region us-east-1
```

Once your new Supabase project is created, use the CLI to link it locally to begin development.

```bash
supabase link --project-ref <project-id>
```

View the [Supabase CLI docs](https://supabase.com/docs/reference/cli) for the full list of available commands.

## Schema migrations

You asked for more database migration support, and we listened. We've made improvements to manually-written migrations and auto generated schema diffs.

### Schema diff-ing

Previously we supported schema diff-ing using PgAdmin. We found that the tool did not account for default privileges when generating role grants. This leads to verbose statements when diff-ing complex schema changes.

To improve the usability of `db diff` command, we are introducing another tool, [migra](https://github.com/djrobstep/migra), via the `--use-migra` experimental flag. We found that `migra` runs faster and produces more concise DDL statements. While the generated scripts are [not perfect](https://supabase.com/blog/supabase-cli#migrations), we hope this tool helps you iterate quicker on your migration scripts. Over time (and with your feedback) we hope to improve schema-diffing to cover all edge-cases.

```bash
$ supabase db diff --use-migra --file file_name
# Creates a DDL script: supabase/migrations/<datetime_string>_file_name.sql
```

Running the above command diffs the `public` schema of your local development database against a fresh shadow database. You may specify other schema by passing in the `--schema` flag multiple times or as a comma separate list. More details in our [migration guide](https://supabase.com/docs/guides/cli/cicd-workflow#creating-a-new-migration).

In the long-term we hope to consolidate on a single diff-ing tool which is perfect, but diff-ing is hard so we would need your help and feedback to improve tooling.

### Manual migrations

You can test manual migrations locally without data loss using local branching. Run the following commands to clone your local database to a new branch.

```bash
supabase db branch new my_branch
supabase db branch switch my_branch
```

Now you can run any DDL statements from Studio UI's SQL Editor. To undo the changes, simply switch back and delete the new branch.

```bash
supabase db branch switch main
supabase db branch delete my_branch
```

## CI / CD

Automating migrations and tests on your CI / CD pipeline gives developers more confidence that each PR contains a working migration script. CLI v1 focuses on both local and [GitHub Actions](https://github.com/supabase/setup-cli) support for the following workflows.

Test all migrations on a fresh local database:

```bash
supabase init
supabase start
```

Release schema changes to staging and production:

```bash
$ supabase link --project-ref $PROJECT_ID
$ supabase db push
```

We created an [example project](https://github.com/supabase/supabase-action-example) showing how to set up GitHub Actions to test and migrate with Supabase CLI v1.

## Type generation

You can now generate TypeScript types using the CLI:

```bash
# in a project set up with the CLI:
supabase gen types typescript --local
supabase gen types typescript --db-url $SUPABASE_DB_URL
```

Of course, the types aren't very useful on its own, you need some way to consume it. For that, stay tuned for tomorrow! 😉

## Management API

The CLI is the first consumer of our new Management API. Over the next few weeks, we'll be adding the endpoints needed to programmatically manage your Supabase projects and organizations. This is ideal for CI/CD workflows and spinning up test environments.

Here's an example of deploying a new project from the command line (generate your access token from the [Supabase Dashboard](https://supabase.com/dashboard/account/tokens)).

```bash
curl 'https://api.supabase.com/v1/projects' \
  -H 'Authorization: Bearer <[your-access-token](https://supabase.com/dashboard/account/tokens)>' \
  -H "Content-Type: application/json" \
  -d '{"name": "my-project", "organization_id": "cool-green-pqdr0qc", "region": "us-east-1", "plan": "free", "db_pass": "********"}'
```

The response JSON will match the example below:

```json
{
  "id": "abcdefghijklmnopqrst",
  "organization_id": "cool-green-pqdr0qc",
  "name": "hello",
  "region": "us-east-1",
  "created_at": "2022-08-12T17:37:11.88819Z"
}
```

Check out the [API docs](https://supabase.com/docs/reference/api) to browse all the functionality added so far.

The new API also opens the door to a whole new suite of integrations, including Zapier, Terraform, Pulumi etc. We're looking forward to seeing how the dev community interacts with these new public endpoints. Try it out and let us know which functionality you'd like to see next.

## More Launch Week 5

- [Launch Week Page](https://supabase.com/launch-week)
- [Launch Week 5 Hackathon](https://supabase.com/blog/launch-week-5-hackathon)
- [Supabase Series B](https://supabase.com/blog/supabase-series-b)
- [Open Source at Supabase - Founders Fireside Chat](https://www.youtube.com/watch?v=4t_63HT3rZY)
- [Day 2 - supabase-js v2 Release Candidate](https://supabase.com/blog/supabase-js-v2)
- [Youtube Video - supabase-js v2 Release Candidate](https://www.youtube.com/watch?v=iqZlPtl_b-I)
- [Day 3 - Supabase is SOC2 compliant](https://supabase.com/blog/supabase-soc2)
- [Youtube video - Security Day](https://www.youtube.com/watch?v=6bGQotxisoY)


## Links discovered
- [install instructions](https://supabase.com/docs/guides/cli)
- [API docs](https://supabase.com/docs/reference/api)
- [Supabase Edge Functions](https://supabase.com/blog/supabase-edge-functions#quickstart)
- [Supabase CLI docs](https://supabase.com/docs/reference/cli)
- [migra](https://github.com/djrobstep/migra)
- [not perfect](https://supabase.com/blog/supabase-cli#migrations)
- [migration guide](https://supabase.com/docs/guides/cli/cicd-workflow#creating-a-new-migration)
- [GitHub Actions](https://github.com/supabase/setup-cli)
- [example project](https://github.com/supabase/supabase-action-example)
- [Supabase Dashboard](https://supabase.com/dashboard/account/tokens)
- [your-access-token](https://supabase.com/dashboard/account/tokens)
- [Launch Week Page](https://supabase.com/launch-week)
- [Launch Week 5 Hackathon](https://supabase.com/blog/launch-week-5-hackathon)
- [Supabase Series B](https://supabase.com/blog/supabase-series-b)
- [Open Source at Supabase - Founders Fireside Chat](https://www.youtube.com/watch?v=4t_63HT3rZY)
- [Day 2 - supabase-js v2 Release Candidate](https://supabase.com/blog/supabase-js-v2)
- [Youtube Video - supabase-js v2 Release Candidate](https://www.youtube.com/watch?v=iqZlPtl_b-I)
- [Day 3 - Supabase is SOC2 compliant](https://supabase.com/blog/supabase-soc2)
- [Youtube video - Security Day](https://www.youtube.com/watch?v=6bGQotxisoY)

--- apps/www/_blog/2022-10-21-supabase-flutter-sdk-v1-released.mdx ---
---
title: 'supabase-flutter v1 Released'
description: We've released supabase-flutter v1. More intuitive way of accessing Supabase from your Flutter application.
author: tyler_shukert
image: flutter-v1-release/flutter_v1_official_release.jpeg
thumb: flutter-v1-release/flutter_v1_official_release.jpeg
categories:
  - engineering
tags:
  - flutter
  - mobile
date: '2022-10-21'
toc_depth: 3
---

A few months ago, we announced a [developer preview version of supabase-flutter SDK](https://supabase.com/blog/supabase-flutter-sdk-1-developer-preview). Since then, we have heard a lot of amazing feedback from the community, and have been improving it. Today, we are happy to announce the stable v1 of [supabase-flutter](https://pub.dev/packages/supabase_flutter). You can also find the updated [quick start guide](https://supabase.com/docs/guides/with-flutter), [documentation](https://supabase.com/docs/reference/dart) and a [migration guide from v0](https://supabase.com/docs/reference/dart/v0/upgrade-guide).

## What is new in v1?

supabase-flutter v1 focuses on improved developer experience. The new version requires far less boiler plate code as well as it provides more intuitive APIs. Here are some highlights of the update.

### No more `.execute()`

Previously, for `.select()`, `.insert()`, `.update()`, `.delete()` and `.stream()` required `execute()` to be called at the end. On top of that, errors are thrown, not returned, so you can be sure that you have the query results in the returned value.

```dart
// Before
final response = await supabase.from('messages').select().execute();
final data = response.data;

// After
final data = await supabase.from('messages').select();
```

### More predictable auth methods

Names of the auth methods are more descriptive about what they do. Here are some examples of the new methods:

```dart
await supabase.auth.signInWithPassword(email: email, password: password);

await supabase.auth.signInWithOAuth(Provider.github)
```

Also, `onAuthStateChange` returns stream, which feels more natural for anyone coding in Dart.

```dart
supabase.auth.onAuthStateChange.listen((data) {
  final AuthChangeEvent event = data.event;
  final Session? session = data.session;
});
```

### Realtime Multiplayer edition support

During the last launch week, we announced the [general availability of Realtime Multiplayer](https://supabase.com/blog/supabase-realtime-multiplayer-general-availability). supabase-flutter now has first class support for the two newly introduced realtime methods, broadcast and presence. Broadcast can be used to share realtime data to all connected clients with low latency. Presence is a way to let other connected clients know the status of the client. You can visit [multiplayer.dev](http://multiplayer.dev) to see a quick demo of the feature.

```dart
final channel = Supabase.instance.client.channel('my_channel');

// listen to `location` broadcast events
channel.on(
    RealtimeListenTypes.broadcast,
    ChannelFilter(
      event: 'location',
    ), (payload, [ref]) {
	// Do something exciting with the broadcast event
});

// send `location` broadcast events
channel.send(
  type: RealtimeListenTypes.broadcast,
  event: 'location',
  payload: {'lat': 1.3521, 'lng': 103.8198},
);

// listen to presence states
channel.on(RealtimeListenTypes.presence, ChannelFilter(event: 'sync'),
    (payload, [ref]) {
	// Do something exciting with the presence state
});

// subscribe to the above changes
channel.subscribe((status) async {
  if (status == 'SUBSCRIBED') {
    // if subscribed successfully, send presence event
    final status = await channel.track({'user_id': myUserId});
  }
});
```

These are just tip of the iceberg of all the updates that we shipped in v1. Check out the [documentation](https://supabase.com/docs/reference/dart/) to see the full list.

## Acknowledgements

It required massive support from the community to bring the supabase-flutter to where it is today. I would like to thank everyone who has contributed to the library, and a special thanks to [Bruno](https://github.com/bdlukaa) and [Vinzent](https://github.com/Vinzent03), who have been key for this release. We really could not have done it without you!

## Resources

- [Install supabase-flutter v1.0](https://pub.dev/packages/supabase_flutter)
- [supabase-flutter documentation](https://supabase.com/docs/reference/dart/)
- [v0 to v1 migration guide](https://supabase.com/docs/reference/dart/v0/upgrade-guide)
- [Flutter Tutorial: building a Flutter chat app](https://supabase.com/blog/flutter-tutorial-building-a-chat-app)
- [Flutter Tutorial - Part 2: Authentication and Authorization with RLS](https://supabase.com/blog/flutter-authentication-and-authorization-with-rls)
- [How to build a real-time multiplayer game with Flutter Flame](https://supabase.com/blog/flutter-real-time-multiplayer-game)
- [Build a Flutter app with Very Good CLI and Supabase](https://verygood.ventures/blog/flutter-app-very-good-cli-supabase)


## Links discovered
- [developer preview version of supabase-flutter SDK](https://supabase.com/blog/supabase-flutter-sdk-1-developer-preview)
- [supabase-flutter](https://pub.dev/packages/supabase_flutter)
- [quick start guide](https://supabase.com/docs/guides/with-flutter)
- [documentation](https://supabase.com/docs/reference/dart)
- [migration guide from v0](https://supabase.com/docs/reference/dart/v0/upgrade-guide)
- [general availability of Realtime Multiplayer](https://supabase.com/blog/supabase-realtime-multiplayer-general-availability)
- [multiplayer.dev](http://multiplayer.dev)
- [documentation](https://supabase.com/docs/reference/dart/)
- [Bruno](https://github.com/bdlukaa)
- [Vinzent](https://github.com/Vinzent03)
- [Install supabase-flutter v1.0](https://pub.dev/packages/supabase_flutter)
- [supabase-flutter documentation](https://supabase.com/docs/reference/dart/)
- [v0 to v1 migration guide](https://supabase.com/docs/reference/dart/v0/upgrade-guide)
- [Flutter Tutorial: building a Flutter chat app](https://supabase.com/blog/flutter-tutorial-building-a-chat-app)
- [Flutter Tutorial - Part 2: Authentication and Authorization with RLS](https://supabase.com/blog/flutter-authentication-and-authorization-with-rls)
- [How to build a real-time multiplayer game with Flutter Flame](https://supabase.com/blog/flutter-real-time-multiplayer-game)
- [Build a Flutter app with Very Good CLI and Supabase](https://verygood.ventures/blog/flutter-app-very-good-cli-supabase)

--- apps/www/_blog/2025-03-20-migrating-mongodb-data-api-with-supabase.mdx ---
---
title: 'Migrating from the MongoDB Data API to Supabase'
description: 'A guide to migrating from the MongoDB Data API to Supabase.'
author: prashant
image: mongodb-data-api/mongodb-data-api.png
thumb: mongodb-data-api/mongodb-data-api-thumb.png
categories:
  - migration
  - postgres
tags:
  - postgres
date: '2025-03-20'
toc_depth: 3
---

MongoDB announced that their Data API and HTTPS Endpoints will reach end-of-life by September 30, 2025. This has left many engineering teams evaluating alternatives. Supabase includes [built-in support for REST APIs](https://supabase.com/features/auto-generated-rest-api) (via PostgREST), mirroring the core functionality previously provided by MongoDB's Data API.

## The implications of MongoDB's Data API deprecation

MongoDB's Data API allowed developers to interact with their Atlas databases through straightforward RESTful endpoints, simplifying integration across frontend, backend, and serverless applications. With its removal, developers must pivot to alternative methods such as native MongoDB drivers combined with backend frameworks (Express, SpringBoot, FastAPI), or third-party solutions like RESTHeart, Hasura, or Neurelo.

This shift requires substantial refactoring for teams that rely on the simplified REST interface.

## Supabase as a MongoDB alternative

Supabase is an open-source MongoDB alternative and offers:

- A managed Postgres database, eliminating vendor lock-in.
- Built-in REST API via open-source PostgREST, directly analogous to MongoDB's Data API.
- Real-time capabilities, authentication, file storage, and serverless functions—all tightly integrated.

Most notably, Supabase [automatically generates a RESTful API](https://supabase.com/docs/guides/api#rest-api-overview) from your database schema, powered by PostgREST, itself a stable open-source project with a strong community. This feature accelerates development by eliminating boilerplate code for basic CRUD operations. Supabase provides a near drop-in replacement for MongoDB’s Data API. The Supabase Data API also supports GraphQL and a host of mobile and web application frameworks.

In the end, you can focus on migrating your data, expose your data using Supabase’s Data API, and seamlessly integrate with your client applications.

## Migrating from MongoDB to Supabase: a step-by-step guide

To migrate to Supabase, you will need to:

1. Export your MongoDB data
2. Import JSON data into Supabase
3. Normalize the data using SQL
4. Transition your existing MongoDB Data API calls to Supabase PostgREST
5. Add more Supabase features to round out your app

### Step 1: Export your MongoDB data

Export MongoDB documents using mongoexport:

```bash
mongoexport --uri="mongodb+srv://<username>:<password>@cluster.mongodb.net/<dbname>" \
  --collection=users \
  --jsonArray \
  --out=users.json
```

### Step 2: Import JSON data into Supabase

Create a table in Supabase with a JSONB column to store raw Mongo documents:

```sql
create table mongo_users_raw (
  id uuid primary key default gen_random_uuid(),
  data jsonb not null
);
```

Then, ingest the exported JSON data into this Supabase table using this custom script:

```jsx
import { createClient } from '@supabase/supabase-js'
import fs from 'fs'

const supabaseUrl = 'YOUR_SUPABASE_URL'
const supabaseKey = 'YOUR_SUPABASE_API_KEY'
const tableName = 'YOUR_TABLE_NAME'
const jsonFilePath = './filename.json'

const supabase = createClient(supabaseUrl, supabaseKey)

async function loadDocumentsToSupabase() {
  try {
    // Read JSON file
    const rawData = fs.readFileSync(jsonFilePath)
    const dataArray = JSON.parse(rawData).map((data) => ({ data }))

    // Insert data into Supabase
    const { error } = await supabase.from(tableName).insert(dataArray)

    if (error) {
      console.error('Error inserting data:', error)
      return
    }

    console.log(`Successfully inserted ${dataArray.length} records into ${tableName}`)
  } catch (error) {
    console.error('Error in process:', error)
  }
}

loadDocumentsToSupabase()
```

### Step 3: Normalize the data using SQL

Once your data is imported as JSONB, leverage PostgreSQL’s powerful JSON functions to incrementally normalize and populate relational tables:

```sql
-- Example to normalize user data
INSERT INTO users (name, email)
SELECT
  data->>'name' as name,
  data->>'email' as email
FROM mongo_users_raw;

-- Example to normalize orders into a separate table
INSERT INTO orders (user_id, product, quantity)
SELECT
  u.id,
  orders->>'product',
  (orders.value->>'quantity')::INTEGER
FROM mongo_users_raw m
JOIN users u ON (m.data->>'name') = u.name,
LATERAL jsonb_array_elements(m.data->'orders') AS order_data
```

### Step 4: Transition API calls to Supabase PostgREST

Once your data has been structured into tables, Supabase automatically generates REST APIs for each table via PostgREST, allowing effortless querying from your application.

For example, if you have a users table, querying user information using Supabase’s JavaScript library would look like this:

```jsx
import { createClient } from '@supabase/supabase-js'

const supabase = createClient('https://<your-project>.supabase.co', '<your-api-key>')

// Fetch user named John along with their orders
const { data, error } = await supabase
  .from('users')
  .select(
    `
    id, name,
    orders (product, quantity)
  `
  )
  .eq('name', 'John')

if (error) {
  console.error(error)
} else {
  console.log(data)
}
```

Using the automatically generated Supabase REST APIs offers a clear migration path from MongoDB’s deprecated Data API.

### Step 5: Add more Supabase features

Once your data is migrated, you can start to use Supabase to its fullest:

- [**Authentication**](https://supabase.com/auth). Let your users login with email, Google, Apple, GitHub, and more. Secure and trusted.
- [**Role-Based Access Control (RBAC)**](https://supabase.com/docs/guides/database/postgres/custom-claims-and-role-based-access-control-rbac). Secure your data properly.
- [**Storage**](https://supabase.com/storage). Affordable and fast, for all the videos and images you need in your app.
- [**Edge Functions**](https://supabase.com/edge-functions). Custom backend logic when you want to dive into code.
- [**Realtime**](https://supabase.com/realtime). Build immersive multi-player, collaborative experiences.
- [**AI Ready**](https://supabase.com/modules/vector). When you’re ready to explore vectors and the power of AI, Supabase is there with industry-standard tools to guide you.
- [**Foreign Data Wrappers (FDW)**](https://supabase.com/docs/guides/database/extensions/wrappers/overview). Pull data from Google Sheets, Airtable, MySQL, and more, as if they were part of Supabase natively.
- **Instant and secure deployment**. No need to set up servers, manage DevOps, or tweak security settings.

## Key considerations and trade-offs

- **Schema flexibility**: PostgreSQL schemas are less flexible than MongoDB; careful upfront design mitigates this.
- **Complexity in migration**: Document structures require transformation scripts.
- **Query patterns**: Analyze existing MongoDB queries to optimize relational joins.
- **Add indexes**: Use the Index Advisor in the Supabase Dashboard to further optimize your tables.

## Conclusion

Supabase is an ideal replacement for MongoDB, especially considering the Data API deprecation. Supabase is built on Postgres, one of the world’s most powerful and scalable databases. In addition, Supabase’s Data API directly parallels MongoDB's Data API, offering a simplified transition path.

[This blog post](https://blog.mansueli.com/migrating-from-mongodb-to-supabase-with-postgresql) provides further detail on how to transition from MongoDB. If you’re encountering difficulty, feel free to reach out to us. We’d be happy to help.

With careful planning and methodical execution, engineering teams can navigate this migration confidently, leveraging Supabase as a trusted, long-term solution.


## Links discovered
- [built-in support for REST APIs](https://supabase.com/features/auto-generated-rest-api)
- [automatically generates a RESTful API](https://supabase.com/docs/guides/api#rest-api-overview)
- [**Authentication**](https://supabase.com/auth)
- [**Role-Based Access Control (RBAC)**](https://supabase.com/docs/guides/database/postgres/custom-claims-and-role-based-access-control-rbac)
- [**Storage**](https://supabase.com/storage)
- [**Edge Functions**](https://supabase.com/edge-functions)
- [**Realtime**](https://supabase.com/realtime)
- [**AI Ready**](https://supabase.com/modules/vector)
- [**Foreign Data Wrappers (FDW)**](https://supabase.com/docs/guides/database/extensions/wrappers/overview)
- [This blog post](https://blog.mansueli.com/migrating-from-mongodb-to-supabase-with-postgresql)

--- apps/www/_blog/2025-04-04-data-api-nearest-read-replica.mdx ---
---
title: 'Data API Routes to Nearest Read Replica'
description: Route your Data API (PostgREST) requests to the nearest Read Replica
author: jose
image: lw14-data-api-nearest-rr/og.png
thumb: lw14-data-api-nearest-rr/thumb.png
categories:
  - launch-week
  - product
tags:
  - launch-week
  - postgrest
date: '2025-04-04T00:00:00'
toc_depth: 3
launchweek: '14'
---

Today we’re releasing Data API requests routing to the nearest Read Replica by extending our [API load balancer](/docs/guides/platform/read-replicas#api-load-balancer) to handle geo-routing.

It’s an impactful improvement that will minimize request latency for your globally distributed applications. It’s available by default when using a load balancer endpoint.

## What is geo-routing?

Geo-routing automatically directs your Data API requests to the geographically closest read replica of your database, reducing latency and improving response times for your users around the world.

<Img
  wide
  alt="Geo-routing diagram"
  src={{
    dark: '/images/blog/lw14-data-api-nearest-rr/geo-routing.png',
    light: '/images/blog/lw14-data-api-nearest-rr/geo-routing-light.png',
  }}
/>

Previously, if you had read replicas in Frankfurt, Singapore, and Virginia, a user located in Europe may experience dramatically different latencies because they could be making requests to any of the replicas.

Our new geo-routing automatically connects users to the nearest read replica so the same user would only make requests to the read replica in the Frankfurt region.

## How geo-routing works

Our geo-routing system uses geospatial algorithms to determine the optimal read replica for each request:

1. Each incoming API request includes geolocation data from the network edge (specifically the `cf.colo` property, which provides the IATA airport code of the datacenter that received the request).
2. We maintain a coordinate mapping system that associates each region where read replicas can be deployed with precise geospatial coordinates.
3. When a request arrives, we calculate the distance between the network edge and each available read replica using the [Haversine formula](https://en.wikipedia.org/wiki/Haversine_formula) (which determines the great-circle distance between two points on a sphere using their longitudes and latitudes).
4. The system automatically routes the request to the geographically closest read replica, minimizing network latency without requiring any configuration on your part.
5. In cases where multiple databases exist in the same region we implement a round-robin strategy to ensure balanced load distribution.

The entire process is completely seamless to your application and users, requiring no changes to your code or configuration besides updating your project URL (`<project_ref>-all.supabase.co`) today.

To get the most from geo-routing, deploy read replicas in regions where your users are concentrated. The more strategically you place your read replicas, the more your users will benefit from reduced latency and improved response times.

## Initial release and roadmap

As an initial release, geo-routing is available with the following limitations:

- Currently limited to read-only Data API (PostgREST) requests

If you're already using our API load balancer there's nothing you need to do; geo-routing is automatically applied to your Data API requests.

Otherwise, you can enable this feature by ensuring your project is using the API load balancer endpoint (`<project_ref>-all.supabase.co`)

We're actively working on expanding geo-routing support to other Supabase products, such as Auth, Storage, and Realtime. Stay tuned for updates.

## Get started today

As always, we welcome your feedback, let us know what you think!

- [Sign up for Supabase](/dashboard/sign-up) and get started today


## Links discovered
- [API load balancer](https://github.com/supabase/supabase/blob/master/docs/guides/platform/read-replicas#api-load-balancer.md)
- [Haversine formula](https://en.wikipedia.org/wiki/Haversine_formula)
- [Sign up for Supabase](https://github.com/supabase/supabase/blob/master/dashboard/sign-up.md)

--- apps/www/_blog/2025-05-17-simplify-backend-with-data-api.mdx ---
---
title: 'Simplifying back-end complexity with Supabase Data APIs'
description: Replace custom CRUD endpoints, reduce infrastructure complexity, and accelerate product delivery.
author: prashant,steve_chavez,laurenceisla
image: 2025-05-17-simplify-backend-with-data-api/simplify-backend-with-data-api-image.png
thumb: 2025-05-17-simplify-backend-with-data-api/simplify-backend-with-data-api-thumb.png
categories:
  - postgres
tags:
  - postgres
  - postgrest
  - graphql
date: '2025-05-17'
---

Behind every modern app is a sprawling backend: dozens of microservices, redundant APIs, managed databases, and gateways stitched together by developers. While this gives engineering teams control, it comes at a steep cost: time, maintenance overhead, and complexity that scales faster than your product.

For many teams, that complexity starts at the data layer. You model your database, write a set of REST endpoints, deploy them to servers, monitor them, patch them, and update them every time your schema changes. Repeat across environments, then across teams.

Supabase offers a different path. By exposing a secure, auto-generated REST and GraphQL API for every table, view, and stored procedure in the public schema in your Postgres database, Supabase compresses weeks of infrastructure work into minutes without sacrificing flexibility or control. This post explores how to use Supabase’s API layer.

## **How Supabase auto-generates APIs from your data model**

Supabase exposes your Postgres database through a [powerful RESTful interface](https://supabase.com/docs/guides/api), auto-generated by [PostgREST](https://postgrest.org/). The moment you create a table or view, Supabase makes it accessible via a fully functional, queryable API with no boilerplate required. These endpoints are structured, predictable, and adhere to industry standards.

Let’s say you define a `customers` table. Instantly, you get:

- GET `/customers` to fetch rows
- POST `/customers` to insert
- PATCH `/customers?id=eq.123` to update
- DELETE `/customers?id=eq.123` to remove

And it doesn’t stop at basic CRUD. Supabase’s API layer supports a rich set of features out of the box:

- Filters and operators for advanced querying
- Pagination and ordering
- Embedded relationships with foreign key joins
- Exposing Postgres functions as RPC endpoints
- GraphQL support
- A [dynamic connection pool](https://docs.postgrest.org/en/v13/references/connection_pool.html) that shrinks and grows based on traffic and whose max pool size grows as the instance size does
- Built-in observability with [Prometheus metrics](https://docs.postgrest.org/en/v13/references/observability.html#metrics)

Supabase automatically generates client libraries based on your schema. For example, here’s a JavaScript example using the official `@supabase/supabase-js` client with auto-generated TypeScript types, querying an e-commerce-style schema with customers, orders, and products:

```tsx
const { data, error } = await supabase
  .from('orders')
  .select(
    `
    id,
    created_at,
    total,
    products (
      id,
      name,
      price
    )
  `
  )
  .eq('customer_id', customerId)
```

## Building custom API endpoints

Supabase provides two powerful options for building custom API endpoints when you need to go beyond standard CRUD operations: Database Functions and Edge Functions.

### Postgres functions

[Database Functions](https://supabase.com/docs/guides/database/functions?queryGroups=language&language=js) (also called stored procedures) allow you to encapsulate complex SQL logic inside the database itself. They are ideal for multi-step transactions, business rules, or performance-sensitive operations that work across multiple tables.

These functions can be exposed via the Supabase API using the `.rpc()` call or accessed directly through the REST endpoint at `/rpc/<function-name>`. Named parameters are passed as a simple JSON payload, making integration clean and declarative.

Here’s an example of a Database Function:

```sql
CREATE FUNCTION calculate_customer_discount(customer_id uuid) RETURNS numeric AS $$
DECLARE
  discount numeric;
BEGIN
  SELECT SUM(amount) * 0.1 INTO discount FROM orders WHERE customer_id = calculate_customer_discount.customer_id;
  RETURN discount;
END;
$$ LANGUAGE plpgsql;
```

And you can call it from the client like this:

```bash
POST /rpc/calculate_customer_discount
{
  ”customer_id”: “uuid-of-customer”
}
```

Here’s the TypeScript example of calling a Database Function using auto-generated types:

```tsx
const { data, error } = await supabase.rpc('calculate_customer_discount', {
  customer_id: 'uuid-of-customer',
})
```

### Supabase Edge Functions

Sometimes you need full flexibility outside the database, For example, you might want to integrate with external APIs or write business logic in TypeScript. For this, you’d want to use [Supabase Edge Functions](https://supabase.com/edge-functions). These are custom serverless functions written in TypeScript and deployed globally at the edge, allowing you to define your own logic and expose it via HTTP.

Each function becomes its own endpoint:

```
https://<project-ref>.functions.supabase.co/<function-name>
```

For example, suppose you want to send a personalized discount email to a customer. You might [create an Edge Function](https://supabase.com/docs/guides/functions) called `send-discount` that could:

- Look up the customer by ID
- Apply business logic to determine eligibility
- Trigger an email via a third-party service
- Log the interaction

You would then call the Edge Function from your code like this:

```tsx
const customerId = 'uuid-of-customer' // Replace with actual customer ID
const projectRef = 'your-project-ref' // e.g. abcdefg.supabase.co
const functionName = 'send-discount'

const response = await fetch(`https://${projectRef}.functions.supabase.co/${functionName}`, {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    Authorization: `Bearer your-access-token`, // From Supabase Auth
  },
  body: JSON.stringify({
    customer_id: customerId,
  }),
})
```

Edge Functions give you full flexibility to write this logic with access to Supabase Auth, your database, and any external APIs.

Use cases include:

- Creating custom checkout flows
- Handling webhooks (e.g. Stripe, OAuth)
- Validating orders before submission
- Integrating with AI tools or internal APIs

Edge Functions complement the auto-generated APIs, offering a path for deeper customization while avoiding the need to host your own backend services.

## **Rethinking architecture with less overhead**

Supabase’s API layer eliminates the need to manually build and maintain middleware to serve data. Instead of managing a fleet of EC2 instances or containers just to provide an interface between your database and your client, Supabase is that interface.

This enables a radically simplified architecture:

- No more internal microservices per table or domain object
- No need to build an API gateway from scratch
- No separate deployment pipelines for frontend and backend developers

Everything is powered by your schema. Add a table, get an API. Change a column, the API reflects it. Need to restrict access? Just define a row-level security (RLS) policy.

For teams moving from hand-built APIs, this can reduce both technical debt and cloud spend. Customers routinely report that Supabase’s managed API layer simplifies onboarding for new developers and cuts build times. [Quilia reduced development time by 75%](https://supabase.com/customers/quilia) with the Supabase Data API.

In practice, Supabase becomes a unified data plane: the single, secure interface for your application logic, internal services, and even external integrations.

## **Controlling access and ensuring security**

Auto-generated does not mean exposed. When you use Postgres’s [Row Level Security](https://supabase.com/docs/guides/auth/row-level-security) (RLS), Supabase’s APIs are [secure by default](https://supabase.com/docs/guides/security/product-security). This means you write policies at the data layer, enforced by the database itself, not in brittle middleware code.

Want to restrict access to rows where `user_id = auth.uid()?` One RLS policy handles it. Want public access to a `products` table but private access to `orders`? Define policies per table.

<Admonition>

Supabase exposes the public schema by default to all users. It also allows mutations on the tables created there by default, unless RLS is specified. Developers should [ensure that RLS is always used for security](https://supabase.com/docs/guides/api/securing-your-api?queryGroups=pre-request&pre-request=use-additional-api-key#enabling-row-level-security).

</Admonition>

Authentication integrates seamlessly via Supabase Auth, which issues [JWTs](https://supabase.com/docs/guides/auth#token) that are passed in every request. These tokens power identity-aware APIs and are validated by PostgREST natively.

Supabase also supports:

- API keys for service-to-service access
- Role-based permissions across environments
- Custom claims and token introspection

From a compliance perspective, Supabase offers [regional project hosting](https://supabase.com/docs/guides/platform/regions) (for example, in London or Frankfurt), dedicated infrastructure per project, and a [shared responsibility model](https://supabase.com/docs/guides/security/soc-2-compliance) that supports GDPR-compliant deployments. Your data remains in your selected region, and Supabase provides Data Processing Agreements, Transfer Impact Assessments, and more.

## **Cost, speed, and maintenance tradeoffs**

Custom API stacks are not just expensive in cloud bills. They are expensive in people hours. Every new endpoint adds scope. Every schema change becomes a deployment task. Every new hire needs to be onboarded into your bespoke architecture.

Supabase flips this equation. You no longer spend time writing endpoints that the platform can generate. You spend it building product.

In terms of cost:

- You reduce infrastructure: fewer compute nodes, no gateways, minimal DevOps
- You reduce time: instant APIs, schema-aligned contracts, no Swagger maintenance
- You reduce risk: fewer moving parts, fewer points of failure, consistent access control

For teams evaluating an architecture consisting of RDS and custom middleware versus Supabase, the total cost of ownership is usually lower with Supabase, though may in some cases converge. But the operational efficiency is not comparable. With Supabase, your backend just works without the constant maintenance burden.

## **Conclusion**

Supabase’s API layer is not just a productivity boost. It is a backend reframe. By removing the need for hand-rolled REST and GraphQL endpoints, Supabase gives developers a secure, scalable, and schema-driven interface to their data.

It reduces infrastructure sprawl. It standardizes how you interact with your backend. And it lets your developers focus on the product, not the plumbing.

Whether you are replacing a fleet of microservices or spinning up a new prototype, Supabase’s auto-generated APIs let you move faster, with fewer errors, and more control.

Ready to try it yourself?

- [Get started with Supabase](https://supabase.com/docs/guides/getting-started)
- [Secure your APIs with RLS](https://supabase.com/docs/guides/auth/row-level-security)
- [Explore our API Reference](https://supabase.com/docs/reference)


## Links discovered
- [powerful RESTful interface](https://supabase.com/docs/guides/api)
- [PostgREST](https://postgrest.org/)
- [dynamic connection pool](https://docs.postgrest.org/en/v13/references/connection_pool.html)
- [Prometheus metrics](https://docs.postgrest.org/en/v13/references/observability.html#metrics)
- [Database Functions](https://supabase.com/docs/guides/database/functions?queryGroups=language&language=js)
- [Supabase Edge Functions](https://supabase.com/edge-functions)
- [create an Edge Function](https://supabase.com/docs/guides/functions)
- [Quilia reduced development time by 75%](https://supabase.com/customers/quilia)
- [Row Level Security](https://supabase.com/docs/guides/auth/row-level-security)
- [secure by default](https://supabase.com/docs/guides/security/product-security)
- [ensure that RLS is always used for security](https://supabase.com/docs/guides/api/securing-your-api?queryGroups=pre-request&pre-request=use-additional-api-key#enabling-row-level-security)
- [JWTs](https://supabase.com/docs/guides/auth#token)
- [regional project hosting](https://supabase.com/docs/guides/platform/regions)
- [shared responsibility model](https://supabase.com/docs/guides/security/soc-2-compliance)
- [Get started with Supabase](https://supabase.com/docs/guides/getting-started)
- [Secure your APIs with RLS](https://supabase.com/docs/guides/auth/row-level-security)
- [Explore our API Reference](https://supabase.com/docs/reference)

--- apps/www/_blog/2025-12-16-metrics-api-observability.mdx ---
---
title: 'Own Your Observability: Supabase Metrics API'
description: 'Stream your Supabase database telemetry into any Prometheus-compatible observability stack with the Metrics API. Full control over monitoring, visualization, and alerting.'
author: steven_eubank
image: 2025-12-16-metrics-api-observability/og.png
thumb: 2025-12-16-metrics-api-observability/thumb.png
categories:
  - product
tags:
  - observability
  - metrics
  - prometheus
  - monitoring
date: '2025-12-16'
toc_depth: 2
---

We've published [enhanced documentation for the Metrics API](https://supabase.com/docs/guides/telemetry/metrics) so that you can stream your Supabase database telemetry into any Prometheus-compatible observability stack. Whether you use Grafana Cloud, Datadog, AWS Managed Prometheus, or a self-hosted setup, the Metrics API gives you full control over how you monitor, visualize, and alert on your database infrastructure.

## Why export your metrics?

Supabase Studio includes built-in observability dashboards. They work well for quick health checks. But production systems rarely exist in isolation.

Your application probably already has an observability stack. APM traces flow into Datadog. Application logs land in Grafana Loki. Infrastructure metrics feed Prometheus. When your database metrics live in a separate silo, you lose context. You cannot correlate a spike in API latency with database connection saturation. You cannot overlay query performance against deployment events.

The Metrics API fixes this. It exposes roughly 200 Postgres performance and health metrics in Prometheus exposition format. One scrape job. One unified view of your entire stack.

## What the Metrics API exposes

Every Supabase project has a metrics endpoint:

```
https://<project-ref>.supabase.co/customer/v1/privileged/metrics
```

Authentication uses HTTP Basic Auth with your service role credentials. The endpoint emits metrics covering:

- **CPU and memory utilization.** Track resource pressure before it becomes a problem.

- **Disk I/O and WAL statistics.** Identify storage bottlenecks and replication lag.

- **Connection pool metrics.** Monitor Supavisor and Postgres connection saturation.

- **Query performance data.** Catch slow queries and index regressions.

The full metric set refreshes every minute. Scrape it once per minute to stay in sync.

## Prometheus native, vendor agnostic

We chose Prometheus exposition format deliberately. It is the lingua franca of cloud-native observability. This single decision unlocks compatibility with:

- **Grafana Cloud.** Their managed Prometheus instance scrapes your endpoint directly. Works on Free and Pro tiers.

- **Datadog.** Configure the Datadog Agent with OpenMetrics integration, or use Prometheus remote write.

- **AWS Managed Prometheus (AMP).** Native AWS integration for teams already in that ecosystem.

- **Grafana self-hosted.** For teams running their own metrics storage.

- **Any Prometheus-compatible backend.** If it speaks PromQL, it works with Supabase.

This is what playing well with open standards looks like. We do not lock your telemetry into a proprietary format. You own your data. You choose your tools.

## What to monitor

With 200 metrics available, where do you start? Here are the signals and [alerts](https://github.com/supabase/supabase-grafana/blob/main/docs/example-alerts.md) that matter most:

- **Connection saturation.** Monitor active connections against your pool limits. Alert before you hit the ceiling.

- **CPU and memory pressure.** Sustained high utilization indicates workload growth or inefficient queries. Correlate with slow query logs to find the culprit.

- **Disk I/O wait.** High I/O wait times suggest storage bottlenecks. This often points to missing indexes or queries that scan too much data.

- **Replication lag.** If you use read replicas, monitor lag to ensure consistency. Alert when lag exceeds acceptable thresholds.

- **Spikes in client connections.** If you see connection metrics crowding your limits or frequent saturation, review how your applications connect. Consider using the [dedicated pooler](https://supabase.com/docs/guides/database/connecting-to-postgres#dedicated-pooler) (PgBouncer) with sane pool sizes instead of many direct connections, especially for chatty or bursty workloads.

- **Spiky workloads.** You may find that you are running periodic jobs during peak traffic hours, use the charts to determine if these spikes are within acceptable bounds or if you could optimise your job scheduling.

- **Development and Maintenance.** Were you running an expensive query on every page load of your app? Use the time range filters to confirm if your optimisations have improved performance and reduced strain on your instance.

## Build alerting that matters

Metrics without alerts are just pretty graphs. The real value comes from automated detection.

- **Right-sizing alerts.** Trigger when CPU or memory consistently exceeds 80% utilization. This gives you time to upgrade before users notice degradation.

- **Saturation alerts.** Fire when connection pools approach capacity. A few minutes of warning lets you investigate before connections start failing.

- **Index regression alerts.** Monitor query performance metrics after deployments. Catch missing indexes before they slow down production traffic.

- **Anomaly detection.** Tools like Datadog can learn normal patterns and alert on deviations. This catches problems you did not anticipate.

- Some more example configurations for alerts on database down, replication lag, and database size can be found in our [GitHub repo](https://github.com/supabase/supabase-grafana/blob/main/docs/example-alerts.md).

## Open source all the way down

The Metrics API reflects how we think about infrastructure. Supabase is built on open source: Postgres, PostgREST, GoTrue, Realtime. Our observability follows the same philosophy.

Prometheus exposition format is an open standard. The `supabase-grafana` repository is MIT licensed. You can fork it, modify it, run it anywhere. No vendor lock-in. No proprietary agents. No data trapped in systems you do not control.

When you outgrow Grafana Cloud, migrate to self-hosted Mimir. When your team standardizes on Datadog, reconfigure the scrape target. Your metrics remain portable because we chose open standards from the start.

## Start monitoring today

The Metrics API is available now on all hosted Supabase projects. Check the [updated documentation](https://supabase.com/docs/guides/telemetry/metrics) for integration guides covering:

- Grafana Cloud setup

- Self-hosted Prometheus configuration

- Datadog Agent integration

- Vendor-agnostic options

You will also notice a new banner in Studio's Observability views. It surfaces these options directly in the interface, making it clear that built-in dashboards are just one choice among many.

Share your dashboards online and tag us on Twitter. We love the inspiration that comes from a cool dashboard layout.


## Links discovered
- [enhanced documentation for the Metrics API](https://supabase.com/docs/guides/telemetry/metrics)
- [alerts](https://github.com/supabase/supabase-grafana/blob/main/docs/example-alerts.md)
- [dedicated pooler](https://supabase.com/docs/guides/database/connecting-to-postgres#dedicated-pooler)
- [GitHub repo](https://github.com/supabase/supabase-grafana/blob/main/docs/example-alerts.md)
- [updated documentation](https://supabase.com/docs/guides/telemetry/metrics)

--- apps/www/lib/api.ts ---
import fs from 'fs'
import { join } from 'path'
import matter from 'gray-matter'

const postsDirectory = join(process.cwd(), '_posts')

export function getPostSlugs() {
  return fs.readdirSync(postsDirectory)
}

export function getPostBySlug(slug: string, fields: string[] = []) {
  const realSlug = slug.replace(/\.md$/, '')
  const fullPath = join(postsDirectory, `${realSlug}.md`)
  const fileContents = fs.readFileSync(fullPath, 'utf8')
  const { data, content } = matter(fileContents)

  type Items = {
    [key: string]: string
  }

  const items: Items = {}

  // Ensure only the minimal needed data is exposed
  fields.forEach((field) => {
    if (field === 'slug') {
      items[field] = realSlug
    }
    if (field === 'content') {
      items[field] = content
    }

    if (data[field]) {
      items[field] = data[field]
    }
  })

  return items
}

export function getAllPosts(fields: string[] = []) {
  const slugs = getPostSlugs()
  const posts = slugs
    .map((slug) => getPostBySlug(slug, fields))
    // sort posts by date in descending order
    .sort((post1, post2) => (post1.date > post2.date ? -1 : 1))
  return posts
}


--- apps/design-system/README.md ---
# Supabase Design System

Design resources for building consistent user experiences at Supabase.

## Getting started

First, make a copy of _.env.local.example_ and name it _env.local_. Then install any required packages and start the development server:

```bash
cd apps/design-system
pnpm i
pnpm dev:full
```

The `dev:full` command runs both the Next.js development server and Contentlayer concurrently, which is recommended for most development workflows.

### Alternative commands

You can also run the development server and content watcher separately:

```bash
# Run only the Next.js development server
pnpm dev

# Run only the content watcher (in a separate terminal shell)
pnpm content:dev
```

Or run the development server from the root directory:

```bash
pnpm dev:design-system
```

To run both the development server and content watcher from the root directory, you can use:

```bash
# Run the development server
pnpm dev:design-system

# Run the content watcher (in a separate terminal shell)
pnpm --filter=design-system content:dev
```

Open [http://localhost:3003](http://localhost:3003) in your browser to see the result.

### Watching for MDX changes

The `dev:full` command automatically watches for changes to MDX files with hot reload. If you're running the `pnpm dev` separately, you'll need to run `pnpm content:dev` in a separate terminal shell to watch for content changes.

### Adding components

The design system _references_ components rather than housing them. That’s an important distinction to make, as everything that follows here is about the documentation of components. You can add or edit components in one of these two places:

- [`packages/ui`](https://github.com/supabase/supabase/tree/master/packages/ui): basic UI components
- [`packages/ui-patterns`](https://github.com/supabase/supabase/tree/master/packages/ui-patterns): components which are built using NPM libraries or amalgamations of components from `patterns/ui`

With that out of the way, there are several parts of this design system that need to be manually updated after components have been added or removed (from documentation). These include:

- `config/docs.ts`: list of components in the sidebar
- `content/docs`: the actual component documentation
- `registry/examples.ts`: list of example components
- `registry/default/example`: the actual example components
- `registry/charts.ts`: chart components
- `registry/fragments.ts`: fragment components

You will probably need to rebuild the design system’s registry after making new additions. You can do that via:

```bash
cd apps/design-system
pnpm build:registry
```


## Links discovered
- [http://localhost:3003](http://localhost:3003)
- [`packages/ui`](https://github.com/supabase/supabase/tree/master/packages/ui)
- [`packages/ui-patterns`](https://github.com/supabase/supabase/tree/master/packages/ui-patterns)

--- apps/design-system/contentlayer.config.js ---
import path from 'path'
import { getHighlighter, loadTheme } from '@shikijs/compat'
import { defineDocumentType, defineNestedType, makeSource } from 'contentlayer2/source-files'
import rehypeAutolinkHeadings from 'rehype-autolink-headings'
import rehypePrettyCode from 'rehype-pretty-code'
import rehypeSlug from 'rehype-slug'
import { codeImport } from 'remark-code-import'
import remarkGfm from 'remark-gfm'
import { visit } from 'unist-util-visit'

import { rehypeComponent } from './lib/rehype-component'
import { rehypeNpmCommand } from './lib/rehype-npm-command'

/** @type {import('contentlayer2/source-files').ComputedFields} */
const computedFields = {
  slug: {
    type: 'string',
    resolve: (doc) => `/${doc._raw.flattenedPath}`,
  },
  slugAsParams: {
    type: 'string',
    resolve: (doc) => doc._raw.flattenedPath.split('/').slice(1).join('/'),
  },
}

const LinksProperties = defineNestedType(() => ({
  name: 'LinksProperties',
  fields: {
    doc: {
      type: 'string',
    },
    api: {
      type: 'string',
    },
  },
}))

const NestedProperties = defineNestedType(() => ({
  name: 'NestedProperties',
  fields: {
    radix: {
      type: 'boolean',
    },
    shadcn: {
      type: 'boolean',
    },
    vaul: {
      type: 'boolean',
    },
    inputOtp: {
      type: 'boolean',
    },
    reactAccessibleTreeview: {
      type: 'boolean',
    },
    recharts: {
      type: 'boolean',
    },
  },
}))

export const Doc = defineDocumentType(() => ({
  name: 'Doc',
  filePathPattern: `docs/**/*.mdx`,
  contentType: 'mdx',
  fields: {
    title: {
      type: 'string',
      required: true,
    },
    description: {
      type: 'string',
      required: true,
    },
    published: {
      type: 'boolean',
      default: true,
    },
    links: {
      type: 'nested',
      of: LinksProperties,
    },
    featured: {
      type: 'boolean',
      default: false,
      required: false,
    },
    component: {
      type: 'boolean',
      default: false,
      required: false,
    },
    fragment: {
      type: 'boolean',
      default: false,
      required: false,
    },
    toc: {
      type: 'boolean',
      default: true,
      required: false,
    },
    source: {
      type: 'nested',
      of: NestedProperties,
    },
  },
  computedFields,
}))

export default makeSource({
  contentDirPath: './content',
  documentTypes: [Doc],
  mdx: {
    remarkPlugins: [remarkGfm, codeImport],
    rehypePlugins: [
      rehypeSlug,
      rehypeComponent,
      () => (tree) => {
        visit(tree, (node) => {
          if (node?.type === 'element' && node?.tagName === 'pre') {
            const [codeEl] = node.children
            if (codeEl.tagName !== 'code') {
              return
            }

            if (codeEl.data?.meta) {
              // Extract event from meta and pass it down the tree.
              const regex = /event="([^"]*)"/
              const match = codeEl.data?.meta.match(regex)
              if (match) {
                node.__event__ = match ? match[1] : null
                codeEl.data.meta = codeEl.data.meta.replace(regex, '')
              }
            }

            node.__rawString__ = codeEl.children?.[0].value
            node.__src__ = node.properties?.__src__
            node.__style__ = node.properties?.__style__
          }
        })
      },
      [
        rehypePrettyCode,
        // rehypePrettyCodeOptions,
        {
          getHighlighter: async () => {
            const theme = await loadTheme(path.join(process.cwd(), '/lib/themes/supabase-2.json'))
            return await getHighlighter({ theme })
          },
          onVisitLine(node) {
            // Prevent lines from collapsing in `display: grid` mode, and allow empty
            // lines to be copy/pasted
            if (node.children.length === 0) {
              node.children = [{ type: 'text', value: ' ' }]
            }
          },
          onVisitHighlightedLine(node) {
            node.properties.className.push('line--highlighted')
          },
          onVisitHighlightedWord(node) {
            node.properties.className = ['word--highlighted']
          },
        },
      ],
      () => (tree) => {
        visit(tree, (node) => {
          if (node?.type === 'element' && node?.tagName === 'div') {
            if (!('data-rehype-pretty-code-fragment' in node.properties)) {
              return
            }

            const preElement = node.children.at(-1)
            if (preElement.tagName !== 'pre') {
              return
            }

            preElement.properties['__withMeta__'] = node.children.at(0).tagName === 'div'
            preElement.properties['__rawString__'] = node.__rawString__

            if (node.__src__) {
              preElement.properties['__src__'] = node.__src__
            }

            if (node.__event__) {
              preElement.properties['__event__'] = node.__event__
            }

            if (node.__style__) {
              preElement.properties['__style__'] = node.__style__
            }
          }
        })
      },
      // rehypeNpmCommand,
      [
        rehypeAutolinkHeadings,
        {
          properties: {
            className: ['subheading-anchor'],
            ariaLabel: 'Link to section',
          },
        },
      ],
    ],
  },
})


--- apps/design-system/tailwind.config.js ---
const config = require('config/tailwind.config')

module.exports = config({
  content: [
    './app/**/*.{js,ts,jsx,tsx}',
    './components/**/*.{js,ts,jsx,tsx}',
    './registry/**/*.{js,ts,jsx,tsx}',
    // purge styles from grid library
    //
    './../../packages/ui/src/**/*.{tsx,ts,js}',
    './../../packages/ui-patterns/src/**/*.{tsx,ts,js}',
  ],
  plugins: [require('@tailwindcss/container-queries')],
  theme: {
    extend: {
      maxWidth: {
        site: '128rem',
      },
    },
  },
})


--- apps/www/_blog/2023-04-07-designing-with-ai-midjourney.mdx ---
---
title: 'Designing with AI: Generating unique artwork for every user'
description: Using MidJourney to generative artwork and serving ticket images with Edge Functions
author: marijana,thor_schaeff
image: launch-week-7/designing-with-ai-midjourney/header.png
thumb: launch-week-7/designing-with-ai-midjourney/header.png
launchweek: '7'
categories:
  - launch-week
tags:
  - launch-week
date: '2023-04-07'
toc_depth: 6
---

While Supabase Launch Weeks happen online, we love the idea of bringing everyone together as if it was an in-person conference. Our ticketing system is one of the ways that we emulate this.

We have been issuing tickets since Launch Week 5, and they were so successful that we have done them ever since. It’s become a challenge to outdo the previous effort with something better every time.

## Prompts with Midjourney

You've probably heard of this "AI" tech, and most likely tired of the low-effort SkyNet memes. We wanted to do something more integrated with our developer/designer workflow. Something that we could share with developers & designers that they might find useful.

[Midjourney](https://www.midjourney.com/) was where we started for art generation. We experimented with prompts, blends, and colors to create eye-catching visuals for each ticket connected to a GitHub profile. Some first attempts and ideas included prompts like:

```
/imagine abstract 3 layers of waves on a dark background with light refracting through it, very cool subtle minimal dark illustration, purple light leak with subtle highlights
```

In our exploration of different commands and prompts, we found that the `/blend` command was particularly enjoyable and helpful for creating the desired visuals. This command allowed us to quickly upload multiple images and experiment with different combinations and aesthetics to create a cohesive new image. For designers, the `/blend` command is like `/imagine` but visual, making it easy to visually experiment with different elements. Experiments included blurred edges, planetoid shapes, light leaks, shadows, swirls, motion blurs, waves, cubes, reflections, bokeh, and lots and lots of _“make it look really really blurred”_.

![midjoruney experiments](/images/blog/launch-week-7/designing-with-ai-midjourney/experiments.jpg)

After exploring a variety of text prompts, blends, and styles, we decided that most of the visuals we generated were too chaotic and busy for our Launch Week tickets. Images felt cluttered and overwhelming, making it difficult to read overlaid text. We shifted our focus to creating a cleaner, simpler aesthetic. This required a selective approach to the visual components we wanted to include.

After what-seemed-like hours (okay, just a few), we were able to generate some interesting artwork that could be useful. We chose purple hues and simpler swirls - minimalist visuals that could serve as backgrounds for our tickets and landing page.

<Img
  src="/images/blog/launch-week-7/designing-with-ai-midjourney/midjourney-variations.png"
  caption="Final AI generated images"
  wide={true}
/>

### Chasing Gold Behind the Scenes

When we were happy with the main visual for Launch Week 7, we also needed to create gold visuals for the extra special tickets, and those needed to look the same… but gold. Initially, we thought we could simply use the prompt `/imagine *seed number* make it gold` to generate the desired effect. To our surprise, MidJourney did not cooperate with this idea:

![midjourney failed attempts.jpg](/images/blog/launch-week-7/designing-with-ai-midjourney/failed-attempts.jpg)

We tried excluding certain elements, such as faces or circles to refine the output, and at one point every prompt ended with a `-- no faces, -- no circles, -- no gold bars ...`, but the occasional gold bars still appeared.

We needed to come up with a different approach (even though this approach seemed more fun).

Eventually, we just added a golden overlay gradient to previously generated images and then used `/blend` to blend the purple and the gold together.

And voila, we had a baseline for generating variations:

![midjourney blending variations](/images/blog/launch-week-7/designing-with-ai-midjourney/midjourney-blending-variations.png)

The final visual that you see everywhere won us over because of its vibrant swirls. While each swirl looks slightly different, a consistent style is still maintained throughout.

Fortunately, we didn't require full-width images for each ticket. Upscaled MidJourney images sufficed. Nevertheless, we had to recreate the main visual on the landing page in vectors to use it effectively. You can view it in full glory at [https://supabase.com/launch-week/7](https://supabase.com/launch-week/7).

![Screenshot 2023-04-06 at 09.47.47.png](/images/blog/launch-week-7/designing-with-ai-midjourney/launch-week-7-ticket-home.png)

## Open Graph images

An important aspect of Launch Week tickets is their shareability. Each Launch Week we’ve been blown away by how many developers post their tickets on social channels (and we absolutely love seeing them!).

When you share your unique ticket URL, the image shown on the social preview is called an Open Graph image (or OG image for short).

These images are generated for each unique URL and ticket. This requires a bit of magic, which in our case means using a [Supabase Edge Function](https://supabase.com/docs/guides/functions) together with [Supabase Storage](https://supabase.com/docs/guides/storage) for smart caching.

### Supabase Edge Function 🤝 Supabase Storage

Our Edge Function handles the generation of each ticket image, and also does a bunch of other things under the hood, like detecting if the ticket was shared on socials. This will become important later!

```jsx
if (userAgent?.toLocaleLowerCase().includes('twitter')) {
  // Attendee shared on Twitter
  await supabaseAdminClient
    .from('lw7_tickets')
    .update({ sharedOnTwitter: 'now' })
    .eq('username', username)
    .is('sharedOnTwitter', null)
} else if (userAgent?.toLocaleLowerCase().includes('linkedin')) {
  // Attendee shared on LinkedIn
  await supabaseAdminClient
    .from('lw7_tickets')
    .update({ sharedOnLinkedIn: 'now' })
    .eq('username', username)
    .is('sharedOnLinkedIn', null)
}
```

We want to be as efficient as possible because generating a png file in an edge function is an expensive operation. We generate each ticket only once and then save it to Supabase Storage (which has a [smart CDN cache built in](https://supabase.com/blog/storage-image-resizing-smart-cdn#smart-cdn-deep-dive)).

So in the first step we check if we can fetch the user’s image from storage:

```tsx
// Try to get image from Supabase Storage CDN.
storageResponse = await fetch(
  `${STORAGE_URL}/tickets/regular/${BUCKET_FOLDER_VERSION}/${username}.png`
)
```

If we can’t find the image in storage, then we kick off the ticket generation pipeline, using Vercel’s awesome [open-source satori library](https://github.com/vercel/satori) transforms HTML & CSS into svgs!

Each image includes the user’s GitHub details. We use [`supabase-js`](https://supabase.com/docs/reference/javascript/installing) for authentication: users log in with their GitHub account and we store their username in a table in Postgres.

Since this table includes email addresses, we secure it using [RLS](https://supabase.com/docs/guides/auth/row-level-security) to ensure each user can only view their own data. At the same time, we want these tickets to be publicly shareable, and that’s where [Postgres Views](https://supabase.com/blog/postgresql-views) come in handy.

By creating a view, we can selectively publicize parts of our table and also compute some additional values on the fly:

```sql
drop view if exists lw7_tickets_golden;

create or replace view lw7_tickets_golden as
  with
    lw7_referrals as (
      select
        referred_by,
        count(*) as referrals
      from lw7_tickets
      where referred_by is not null
      group by referred_by
    )
  select
    lw7_tickets."id",
    lw7_tickets."name",
    lw7_tickets."username",
    lw7_tickets."ticketNumber",
    lw7_tickets."createdAt",
    lw7_tickets."sharedOnTwitter",
    lw7_tickets."sharedOnLinkedIn",
    lw7_tickets."bg_image_id",
    case
      when lw7_referrals.referrals is null then 0
      else lw7_referrals.referrals
    end as referrals,
    case
      when lw7_tickets."sharedOnTwitter" is not null
      and lw7_tickets."sharedOnLinkedIn" is not null then true
      else false
    end as golden
  from
    lw7_tickets
    left outer join lw7_referrals on lw7_tickets.username = lw7_referrals.referred_by;

select *
from lw7_tickets_golden;
```

We can now retrieve that username by using the following code:

```jsx
// Get ticket data
const { data, error } = await supabaseAdminClient
  .from('lw7_tickets_golden')
  .select('name, ticketNumber, golden, bg_image_id')
  .eq('username', username)
  .maybeSingle()
if (error) console.log(error.message)
if (!data) throw new Error('user not found')
const { name, ticketNumber, bg_image_id } = data
const golden = data?.golden ?? false
```

You can now probably guess why our edge function was tracking requests from the Twitter and LinkedIn bots! That’s exactly the condition used to turn your ticket golden. How cool is that, with the power of Postgres, we can do all of this within the Database, absolutely mind-blowing. Also, we can easily track a referral count. Relational DBs for the win!

With our public view in place, we can now easily retrieve the relevant ticket details needed to generate the image, via `supabase-js`:

The ticket image itself is just a layering of some background images, your GitHub profile picture, and some text elements, et voila you’ve got yourself a unique ticket image!

<div class="grid grid-cols-2 gap-x-5 md:gap-x-8 [&>figure]:!m-0">
<figure>
  ![Main background image](/images/blog/launch-week-7/designing-with-ai-midjourney/ticket-backdrop.png)
  <figcaption>
    Main background image
  </figcaption>
</figure>

<figure>
  ![Ticket outline](/images/blog/launch-week-7/designing-with-ai-midjourney/ticket-outline.png)
  <figcaption>Ticket outline</figcaption>
</figure>

<figure>
  ![Random AI generated background for the ticket
  outline](/images/blog/launch-week-7/designing-with-ai-midjourney/ticket-ai-background.png)
  <figcaption>Random AI generated background for the ticket outline</figcaption>
</figure>

<figure>
  ![Layer it all together and put some nice text on top and you got yourself a beautiful
  ticket!](/images/blog/launch-week-7/designing-with-ai-midjourney/ticket-details.png)
  <figcaption>Layer it all together and put some nice text on top and you got yourself a beautiful ticket!</figcaption>
</figure>
</div>

Once generated, we can conveniently upload the image to Supabase Storage using `supabase-js`. This ensures fast response times as well as efficient resource usage.

```jsx
const type = golden ? 'golden' : 'regular'

// Upload image to storage.
const { error: storageError } = await supabaseAdminClient.storage
  .from('images')
  .upload(`lw7/tickets/${type}/${BUCKET_FOLDER_VERSION}/${username}.png`, generatedImage.body, {
    contentType: 'image/png',
    cacheControl: '31536000',
    upsert: false,
  })
```

And of course, all of this is open source, you can find the full function code [here](https://github.com/supabase/supabase/tree/master/apps/www/supabase/functions/lw7-ticket-og). Please feel free to utilize it for your own Launches!

### Turning tickets golden in realtime

Using the power of the entire Supabase stack, we’ve designed a pretty neat mechanic to allow users to turn their tickets golden.

In previous Launch Weeks, we employed the [fibonacci sequence](https://en.wikipedia.org/wiki/Fibonacci_sequence) to sprinkle golden tickets around using the ticket number sequence. This time around we wanted to make it more interactive and allow the user to earn their golden ticket, increasing their chance to win swag.

Remember the Twitter and LinkedIn bot detection from above? We use those to generate the `golden` column in our public view:

```sql
case
  when lw7_tickets."sharedOnTwitter" is not null
  and lw7_tickets."sharedOnLinkedIn" is not null then true
  else false
end as golden
```

As folks are sharing their tickets on socials to earn their gold status, we also want to give them realtime feedback on their progress. Luckily we also have a feature for that, [Supabase Realtime](https://supabase.com/realtime).

Something that would have been a headache in the past is just a couple of lines of client-side JavaScript:

```jsx
const channel = supabase
  .channel('changes')
  .on(
    'postgres_changes',
    {
      event: 'UPDATE',
      schema: 'public',
      table: 'lw7_tickets',
      filter: `username=eq.${username}`,
    },
    (payload) => {
      const golden = !!payload.new.sharedOnTwitter && !!payload.new.sharedOnLinkedIn
      setUserData({
        ...payload.new,
        golden,
      })
      if (golden) {
        channel.unsubscribe()
      }
    }
  )
  .subscribe()
```

Interested to know how this fits within your Next.js application? Find the code [here](https://github.com/supabase/supabase/blob/master/apps/www/components/LaunchWeek/7/Ticket/TicketForm.tsx#L70-L102).

## Displaying the images

We have some images, so how can we now display them somewhere? [@Francesco Sansalvadore](https://twitter.com/frnk_snslvdr) saw one of our team members trying to “swipe” on the tickets slider component on the [launch week page](https://supabase.com/launch-week/7/tickets) and he thought, why not feature all the fantastic people who generated tickets on a single page?

We built a [ticket wall](https://supabase.com/launch-week/7/tickets) that you can scroll endlessly. We approached it with an infinite scroll technique, lazy-loading a few tickets at a time.

![launch week 7 ticket wall](/images/blog/launch-week-7/designing-with-ai-midjourney/launch-week-7-ticket-wall.png)

If you’re interested in a more detailed step-by-step guide to reproduce this effect, take a look at the [Infinite scroll with Next.js, Framer Motion, and Supabase](/blog/infinite-scroll-with-nextjs-framer-motion) blog post.

## Get your ticket

You too can also be Charlie Bucket and have a Golden Ticket. There is no chocolate factory, however, but we do have some amazing swag to win.

Up for grabs are:

- Supabase mechanical keyboard. In fact, we have 3 of them to give away!\_ Guaranteed to annoy your co-workers/cat/partner/ yourself.
- Socks: _Perfect for your `<footer>`. Right?!.. anyway._
- T-shirts - _Just don’t put them in a tumble dryer_
- and; of course a bunch of stickers.

## More Supabase AI reading

- [Supabase storing OpenAI embeddings in Postgres with pgvector](https://supabase.com/blog/openai-embeddings-postgres-vector)
- [Supabase Docs Search](http://supabase.com/docs)
- [Streaming Data in Edge Functions](https://www.youtube.com/watch?v=9N66JBRLNYU)
- [Next.js OpenAI Doc Search template](https://github.com/supabase-community/nextjs-openai-doc-search)


## Links discovered
- [Midjourney](https://www.midjourney.com/)
- [midjoruney experiments](https://github.com/supabase/supabase/blob/master/images/blog/launch-week-7/designing-with-ai-midjourney/experiments.jpg)
- [midjourney failed attempts.jpg](https://github.com/supabase/supabase/blob/master/images/blog/launch-week-7/designing-with-ai-midjourney/failed-attempts.jpg)
- [midjourney blending variations](https://github.com/supabase/supabase/blob/master/images/blog/launch-week-7/designing-with-ai-midjourney/midjourney-blending-variations.png)
- [https://supabase.com/launch-week/7](https://supabase.com/launch-week/7)
- [Screenshot 2023-04-06 at 09.47.47.png](https://github.com/supabase/supabase/blob/master/images/blog/launch-week-7/designing-with-ai-midjourney/launch-week-7-ticket-home.png)
- [Supabase Edge Function](https://supabase.com/docs/guides/functions)
- [Supabase Storage](https://supabase.com/docs/guides/storage)
- [smart CDN cache built in](https://supabase.com/blog/storage-image-resizing-smart-cdn#smart-cdn-deep-dive)
- [open-source satori library](https://github.com/vercel/satori)
- [`supabase-js`](https://supabase.com/docs/reference/javascript/installing)
- [RLS](https://supabase.com/docs/guides/auth/row-level-security)
- [Postgres Views](https://supabase.com/blog/postgresql-views)
- [Main background image](https://github.com/supabase/supabase/blob/master/images/blog/launch-week-7/designing-with-ai-midjourney/ticket-backdrop.png)
- [Ticket outline](https://github.com/supabase/supabase/blob/master/images/blog/launch-week-7/designing-with-ai-midjourney/ticket-outline.png)
- [Random AI generated background for the ticket
  outline](https://github.com/supabase/supabase/blob/master/images/blog/launch-week-7/designing-with-ai-midjourney/ticket-ai-background.png)
- [Layer it all together and put some nice text on top and you got yourself a beautiful
  ticket!](https://github.com/supabase/supabase/blob/master/images/blog/launch-week-7/designing-with-ai-midjourney/ticket-details.png)
- [here](https://github.com/supabase/supabase/tree/master/apps/www/supabase/functions/lw7-ticket-og)
- [fibonacci sequence](https://en.wikipedia.org/wiki/Fibonacci_sequence)
- [Supabase Realtime](https://supabase.com/realtime)
- [here](https://github.com/supabase/supabase/blob/master/apps/www/components/LaunchWeek/7/Ticket/TicketForm.tsx#L70-L102)
- [@Francesco Sansalvadore](https://twitter.com/frnk_snslvdr)
- [launch week page](https://supabase.com/launch-week/7/tickets)
- [ticket wall](https://supabase.com/launch-week/7/tickets)
- [launch week 7 ticket wall](https://github.com/supabase/supabase/blob/master/images/blog/launch-week-7/designing-with-ai-midjourney/launch-week-7-ticket-wall.png)
- [Infinite scroll with Next.js, Framer Motion, and Supabase](https://github.com/supabase/supabase/blob/master/blog/infinite-scroll-with-nextjs-framer-motion.md)
- [Supabase storing OpenAI embeddings in Postgres with pgvector](https://supabase.com/blog/openai-embeddings-postgres-vector)
- [Supabase Docs Search](http://supabase.com/docs)
- [Streaming Data in Edge Functions](https://www.youtube.com/watch?v=9N66JBRLNYU)
- [Next.js OpenAI Doc Search template](https://github.com/supabase-community/nextjs-openai-doc-search)

--- apps/www/_blog/2023-12-08-how-design-works-at-supabase.mdx ---
---
title: 'How design works at Supabase'
description: "The transformative journey of Supabase's Design team and its unique culture to enhance the output and quality of the entire company."
launchweek: X
categories:
  - company
tags:
  - launch-week
  - design
date: '2023-12-08'
toc_depth: 3
author: jonny,marijana,fsansalvadore
image: lwx-how-design-works-at-supabase/og.png
thumb: lwx-how-design-works-at-supabase/design-at-supabase-thumb.jpg
---

After three years, only now are we figuring out “what design is” for Supabase and how it functions in the wider org.

We recently became a team of three, developing a somewhat-unique culture to increase the output and quality of our own team and the product teams. Here are a few insights into what we’ve learned along the way.

## Our approach to design

To design at Supabase, you have to think like an agile developer.

What minimal increment will have the biggest impact, with the lowest engineering effort? We make small daily gains while simultaneously solving large milestones. We aim to [ship daily and dream in years.](https://twitter.com/DJ44/status/819316928623902720?ref_src=twsrc%5Etfw%7Ctwcamp%5Etweetembed%7Ctwterm%5E819316928623902720%7Ctwgr%5E27904ffca0505db58a08f405f950312e7de970d3%7Ctwcon%5Es1_c10&ref_url=https%3A%2F%2Fsensible.blog%2F2020%2F11%2F24%2Fdream-in-years-plan-in-months-ship-in-days%2F)

The emphasis is on the daily gains, unblocking problems with design work so that no team is paralyzed by how something should work, function or look. This can manifest in: static mockups, interactive prototypes in Figma, code prototypes, wireframe sketches, and sometimes we actually start building it (👀). We do any work that can help the organization build consensus.

Let’s take an example, our LWX ticket page:

<Img
  src="/images/blog/lwx-how-design-works-at-supabase/lwx-tickets-iterations.png"
  wide
  className="[&_img]:border-0"
/>

Late in the build process an idea was floated to try and integrate a _Wordle_ type easter egg into the site. We build some designs to help the discussion:

<div className="grid grid-cols-2 gap-3">

![something](/images/blog/lwx-how-design-works-at-supabase/lwx-ticket-1.png)

![something](/images/blog/lwx-how-design-works-at-supabase/lwx-ticket-2.png)

</div>

The left image is Figma where as the right is Prod. The team aligned quickly on an outcome and aesthetic and then we iterate, simplify, and evolve the concept as we go.

The designs helped to avoid [bikeshedding](https://en.wiktionary.org/wiki/bikeshedding)—confining the engineering discussions to “how will someone find this” or “will someone use this”.

### Iterative changes

After we have shipped to prod, design work becomes expired. Whatever is mocked before no longer serves a purpose.

We took some inspiration from [Linear’s approach](https://x.com/karrisaarinen/status/1715091479184736629?s=20)—taking screenshots of what is in prod, and using the screenshots to construct mockups.

Here’s an example where we are making changes to the Table Editor:

<Img
  src="/images/blog/lwx-how-design-works-at-supabase/screenshots-in-mockups.png"
  wide
  caption="A screenshot of the Table Editor from prod in a mockup"
/>

We simply screenshot what is in prod, (in this case the cells in the Table Editor) and then overlay any Figma elements on top to quickly iterate. We don’t need to keep re-building UI components in Figma just to keep up with what’s in prod.

## Principle led

For a long time we operated without any sort of consensus of what Design _is_ at Supabase: what are our values, what do we like, what do we not like?

It’s surprising how far you can get without these. After three years, we noticed that we were drifting into contradictory aesthetics and it was becoming challenging to support other teams.

When Supabase was founded, the team agreed on a set of [Product Principles](https://supabase.com/docs/guides/getting-started/architecture#product-principles). Internally, we’ve expanded this concept to all teams, products, and functions at Supabase.

The Design team adopted and maintain two sets of principles:

- [Aesthetic Principles](#aesthetic-principles)
- [Product Design Principles](#product-design-principles)

### Aesthetic principles

We needed to agree on what we collectively like and dislike on an emotional level. Aligning on a common aesthetic avoids debates on individual elements.

<Img
  src="/images/blog/lwx-how-design-works-at-supabase/aesthetic-moodboard.png"
  wide
  caption="Aesthetic mood board of what Design team like externally"
/>

After the team voted on their likes and dislikes, patterns begin to emerge: colors, tones, shapes, typography, layout, etc.

We used these preferences to build higher-level alignment, leading to our founding principles such as:

#### Timelessness

- We always ask critically: will this still feel good in a few years?
- Are we following a trend we’ve recently seen?
- _We buck trends, not follow them._

#### Less is more

- Always remove the fluff
- Remove overbearing information
- [Dieter Rams](https://en.wikipedia.org/wiki/Dieter_Rams#Less_and_More_exhibition) ftw

We enforce these principles by following agreed tactics: mantras such as “be more subtle”, “simplify simplify simplify”, “use brand green only for CTA”.

This has helped the team push in a unified direction. Perhaps you have already noticed recent updates? They are more fine-tuned, more subtle, use less green, and so on.

### Product design principles

Product Principles are about collaborating with Product teams. We came up with a list of principles that we think are important for effective velocity. To share just a few of our favorites:

#### Design for the "Postgres developer"

We are moving towards a SQL-first experience in the dashboard. You’ll see some LWX announcements that lean more into empowering developers to learn SQL.

#### Consolidation follows kaizen

Product Teams must ship _[kaizen](https://en.wikipedia.org/wiki/Kaizen)_ improvements. But it’s the Design team’s role to consolidate after. This helps with velocity: we are not a blocker in the development process.

For example, yesterday the Frontend team added a “New organization” button in the Dashboard based on a message from Kevin (see below). They skipped Design team input and shipped a quick solution. The Design team can do a more refined layout update after LWX, improving the page as a whole.

![something](/images/blog/lwx-how-design-works-at-supabase/kaizen-before-consildation.png)

#### 80/20

We bias towards the 80% of developers using Supabase. Following the [Pareto Principle](https://www.investopedia.com/terms/p/paretoprinciple.asp), we focus on the smallest changes that have the largest impact on our userbase.

This isn’t always easy, since there is usually a vocal minority.

#### Build patterns

Repeatable actions that enforce muscle memory will always be preferred. When adding new features, (or, most likely nowadays; _overhauling features_) we always consider how the same UI patterns can work in other areas.

You will start to notice these principles being applied more throughout the LWX features announced this week.

## Tools and Tactics

We use very few tools (across the entire org), and we build good processes and practices around them. Let’s review a couple for the Design team:

### Figma

No introduction required. We’ve used Figma since the beginning and will continue to be our workhorse for application UI design and marketing design.

We’ve organized our Figma library into quarterly files, that are clearly labelled as “in progress” or “archived” so anyone can easily find the latest and greatest.

<Img src="/images/blog/lwx-how-design-works-at-supabase/figma-organization.png" wide={true} />

### Design system

We started maintaining a library during LW5, but only recently started using it in earnest. We’ve kept the system deliberately small, only adding what we’ve used more than a few times. The library then doesn’t spread into unique use-cases and suffer from content bloat.

<Img
  src="/images/blog/lwx-how-design-works-at-supabase/figma-design-system.png"
  wide
  className="[&_img]:border-0"
/>

Luckily, Figma features such as [nested components](https://www.figma.com/best-practices/component-architecture/#nested-components) came out while we were revamping this set of components. This meant we could reduce the footprint of the design system significantly. Figma libraries previously required every permutation of a component, but it’s now easy to contain things like swappable “icons” or pseudo states like “active” within components.

## The Design Engineer

They say designers who code are unicorns. So we found a few.

At some point in many Designer’s careers they become so frustrated with the speed of development and decide, _enough is enough_, _I’m not satisfied_, _I’m going to learn JavaScript!_

Or, it’s the other way round, a Developer is frustrated at designs handed to them, and decide it’s time to figure this out themselves.

We are describing a Design Engineer: The Unicorn.

Several team members now fit the “Design Engineer” description and it has enabled rapid shipping. Design doesn’t stop at wireframes: often some of the best iterations happen in code. Design work is treated as a reference more than a pixel-perfect outcome. With multiple Design Engineers, they are all co-owners, fine tuning what matters and what inevitably ends up in production.

### Design files that update prod

Just today we saw this tweet:

<a href="https://x.com/soleio/status/1732082949670023373?s=20" target="_black" className="!m-0">
  <figure className="max-w-md mx-auto !my-0">
    ![something](/images/blog/lwx-how-design-works-at-supabase/soleio-tweet.png)
  </figure>
</a>

Today, this statement is true. But does it need to be? Can we update production from Figma? The answer is, yes; partly.

At Supabase, we have a pipeline that exports [Figma variables](https://help.figma.com/hc/en-us/articles/15339657135383-Guide-to-variables-in-Figma) into [CSS custom properties](https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties), used with TailwindCSS. What does this mean? Developing apps with TailwindCSS in the main monorepo uses the same color palette as our files in Figma.

Our Tailwind can now be used like this:

`background.default` in TailwindCSS is `"bg"`

`background.alternative` in TailwindCSS is `“bg-alternative”`

`foreground.DEFAULT` in TailwindCSS is `”text”`

`foreground.light` in TailwindCSS is `“text-light”`

Now we have a fully sync’d color system between design files and our actual development environment. We can also expand into other properties such as spacing, sizes, typography and so on.

## Team of three

The team has been kept small, and deliberately! Only adding people when we hit a resource tipping point. Today, we only have three people.

Jonny was the first team member in Supabase with any design background. We (just) survived until before LW5, when Marijana joined. And before LW7, Francesco joined. You may have noticed the quality creeping up while also accelerating recently.

All three compliment each other: while one shines in visual design, another does in product design, another in motion design. Though we also overlap enough in a way that helps to keep the ball rolling in an async setting.

### I like this, how do I join?

We’re always on the look out for talent, even if there’s no [job posting](/careers), reach out to [Jonny](https://twitter.com/JSummersMuir), [Marijana](https://twitter.com/marijanapav) or [Francesco](https://twitter.com/frnk_snslvdr) on Twitter.


## Links discovered
- [ship daily and dream in years.](https://twitter.com/DJ44/status/819316928623902720?ref_src=twsrc%5Etfw%7Ctwcamp%5Etweetembed%7Ctwterm%5E819316928623902720%7Ctwgr%5E27904ffca0505db58a08f405f950312e7de970d3%7Ctwcon%5Es1_c10&ref_url=https%3A%2F%2Fsensible.blog%2F2020%2F11%2F24%2Fdream-in-years-plan-in-months-ship-in-days%2F)
- [something](https://github.com/supabase/supabase/blob/master/images/blog/lwx-how-design-works-at-supabase/lwx-ticket-1.png)
- [something](https://github.com/supabase/supabase/blob/master/images/blog/lwx-how-design-works-at-supabase/lwx-ticket-2.png)
- [bikeshedding](https://en.wiktionary.org/wiki/bikeshedding)
- [Linear’s approach](https://x.com/karrisaarinen/status/1715091479184736629?s=20)
- [Product Principles](https://supabase.com/docs/guides/getting-started/architecture#product-principles)
- [Dieter Rams](https://en.wikipedia.org/wiki/Dieter_Rams#Less_and_More_exhibition)
- [kaizen](https://en.wikipedia.org/wiki/Kaizen)
- [something](https://github.com/supabase/supabase/blob/master/images/blog/lwx-how-design-works-at-supabase/kaizen-before-consildation.png)
- [Pareto Principle](https://www.investopedia.com/terms/p/paretoprinciple.asp)
- [nested components](https://www.figma.com/best-practices/component-architecture/#nested-components)
- [something](https://github.com/supabase/supabase/blob/master/images/blog/lwx-how-design-works-at-supabase/soleio-tweet.png)
- [Figma variables](https://help.figma.com/hc/en-us/articles/15339657135383-Guide-to-variables-in-Figma)
- [CSS custom properties](https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties)
- [job posting](https://github.com/supabase/supabase/blob/master/careers.md)
- [Jonny](https://twitter.com/JSummersMuir)
- [Marijana](https://twitter.com/marijanapav)
- [Francesco](https://twitter.com/frnk_snslvdr)
- [<figure className="max-w-md mx-auto !my-0"> ![something](/images/blog/lwx-how-design-works-at-supabase/soleio-tweet.png) </figure>](https://x.com/soleio/status/1732082949670023373?s=20)

--- apps/design-system/registry/charts.ts ---
import { Registry } from './schema'

export const charts: Registry = [
  {
    name: 'chart-bar-interactive',
    type: 'components:block',
    registryDependencies: ['card', 'chart'],
    files: ['block/chart-bar-interactive.tsx'],
    category: 'Charts',
    subcategory: 'Bar',
  },
  {
    name: 'chart-composed-demo',
    type: 'components:block',
    registryDependencies: ['chart'],
    files: ['block/chart-composed-demo.tsx'],
    category: 'Charts',
    subcategory: 'Composed',
  },
  {
    name: 'chart-composed-basic',
    type: 'components:block',
    registryDependencies: ['chart'],
    files: ['block/chart-composed-basic.tsx'],
    category: 'Charts',
    subcategory: 'Composed',
  },
  {
    name: 'chart-composed-states',
    type: 'components:block',
    registryDependencies: ['chart'],
    files: ['block/chart-composed-states.tsx'],
    category: 'Charts',
    subcategory: 'Composed',
  },
  {
    name: 'chart-composed-table',
    type: 'components:block',
    registryDependencies: ['chart'],
    files: ['block/chart-composed-table.tsx'],
    category: 'Charts',
    subcategory: 'Composed',
  },
  {
    name: 'chart-composed-metrics',
    type: 'components:block',
    registryDependencies: ['chart'],
    files: ['block/chart-composed-metrics.tsx'],
    category: 'Charts',
    subcategory: 'Composed',
  },
  {
    name: 'chart-composed-actions',
    type: 'components:block',
    registryDependencies: ['chart'],
    files: ['block/chart-composed-actions.tsx'],
    category: 'Charts',
    subcategory: 'Composed',
  },
]


--- apps/design-system/registry/colors.ts ---
export const colors = {
  inherit: 'inherit',
  current: 'currentColor',
  transparent: 'transparent',
  black: {
    hex: '#000000',
    rgb: 'rgb(0,0,0)',
    hsl: 'hsl(0,0%,0%)',
  },
  white: {
    hex: '#ffffff',
    rgb: 'rgb(255,255,255)',
    hsl: 'hsl(0,0%,100%)',
  },
  slate: [
    {
      scale: 50,
      hex: '#f8fafc',
      rgb: 'rgb(248,250,252)',
      hsl: 'hsl(210,40%,98%)',
    },
    {
      scale: 100,
      hex: '#f1f5f9',
      rgb: 'rgb(241,245,249)',
      hsl: 'hsl(210,40%,96.1%)',
    },
    {
      scale: 200,
      hex: '#e2e8f0',
      rgb: 'rgb(226,232,240)',
      hsl: 'hsl(214.3,31.8%,91.4%)',
    },
    {
      scale: 300,
      hex: '#cbd5e1',
      rgb: 'rgb(203,213,225)',
      hsl: 'hsl(212.7,26.8%,83.9%)',
    },
    {
      scale: 400,
      hex: '#94a3b8',
      rgb: 'rgb(148,163,184)',
      hsl: 'hsl(215,20.2%,65.1%)',
    },
    {
      scale: 500,
      hex: '#64748b',
      rgb: 'rgb(100,116,139)',
      hsl: 'hsl(215.4,16.3%,46.9%)',
    },
    {
      scale: 600,
      hex: '#475569',
      rgb: 'rgb(71,85,105)',
      hsl: 'hsl(215.3,19.3%,34.5%)',
    },
    {
      scale: 700,
      hex: '#334155',
      rgb: 'rgb(51,65,85)',
      hsl: 'hsl(215.3,25%,26.7%)',
    },
    {
      scale: 800,
      hex: '#1e293b',
      rgb: 'rgb(30,41,59)',
      hsl: 'hsl(217.2,32.6%,17.5%)',
    },
    {
      scale: 900,
      hex: '#0f172a',
      rgb: 'rgb(15,23,42)',
      hsl: 'hsl(222.2,47.4%,11.2%)',
    },
    {
      scale: 950,
      hex: '#020617',
      rgb: 'rgb(2,6,23)',
      hsl: 'hsl(222.2,84%,4.9%)',
    },
  ],
  gray: [
    {
      scale: 50,
      hex: '#f9fafb',
      rgb: 'rgb(249,250,251)',
      hsl: 'hsl(210,20%,98%)',
    },
    {
      scale: 100,
      hex: '#f3f4f6',
      rgb: 'rgb(243,244,246)',
      hsl: 'hsl(220,14.3%,95.9%)',
    },
    {
      scale: 200,
      hex: '#e5e7eb',
      rgb: 'rgb(229,231,235)',
      hsl: 'hsl(220,13%,91%)',
    },
    {
      scale: 300,
      hex: '#d1d5db',
      rgb: 'rgb(209,213,219)',
      hsl: 'hsl(216,12.2%,83.9%)',
    },
    {
      scale: 400,
      hex: '#9ca3af',
      rgb: 'rgb(156,163,175)',
      hsl: 'hsl(217.9,10.6%,64.9%)',
    },
    {
      scale: 500,
      hex: '#6b7280',
      rgb: 'rgb(107,114,128)',
      hsl: 'hsl(220,8.9%,46.1%)',
    },
    {
      scale: 600,
      hex: '#4b5563',
      rgb: 'rgb(75,85,99)',
      hsl: 'hsl(215,13.8%,34.1%)',
    },
    {
      scale: 700,
      hex: '#374151',
      rgb: 'rgb(55,65,81)',
      hsl: 'hsl(216.9,19.1%,26.7%)',
    },
    {
      scale: 800,
      hex: '#1f2937',
      rgb: 'rgb(31,41,55)',
      hsl: 'hsl(215,27.9%,16.9%)',
    },
    {
      scale: 900,
      hex: '#111827',
      rgb: 'rgb(17,24,39)',
      hsl: 'hsl(220.9,39.3%,11%)',
    },
    {
      scale: 950,
      hex: '#030712',
      rgb: 'rgb(3,7,18)',
      hsl: 'hsl(224,71.4%,4.1%)',
    },
  ],
  zinc: [
    {
      scale: 50,
      hex: '#fafafa',
      rgb: 'rgb(250,250,250)',
      hsl: 'hsl(0,0%,98%)',
    },
    {
      scale: 100,
      hex: '#f4f4f5',
      rgb: 'rgb(244,244,245)',
      hsl: 'hsl(240,4.8%,95.9%)',
    },
    {
      scale: 200,
      hex: '#e4e4e7',
      rgb: 'rgb(228,228,231)',
      hsl: 'hsl(240,5.9%,90%)',
    },
    {
      scale: 300,
      hex: '#d4d4d8',
      rgb: 'rgb(212,212,216)',
      hsl: 'hsl(240,4.9%,83.9%)',
    },
    {
      scale: 400,
      hex: '#a1a1aa',
      rgb: 'rgb(161,161,170)',
      hsl: 'hsl(240,5%,64.9%)',
    },
    {
      scale: 500,
      hex: '#71717a',
      rgb: 'rgb(113,113,122)',
      hsl: 'hsl(240,3.8%,46.1%)',
    },
    {
      scale: 600,
      hex: '#52525b',
      rgb: 'rgb(82,82,91)',
      hsl: 'hsl(240,5.2%,33.9%)',
    },
    {
      scale: 700,
      hex: '#3f3f46',
      rgb: 'rgb(63,63,70)',
      hsl: 'hsl(240,5.3%,26.1%)',
    },
    {
      scale: 800,
      hex: '#27272a',
      rgb: 'rgb(39,39,42)',
      hsl: 'hsl(240,3.7%,15.9%)',
    },
    {
      scale: 900,
      hex: '#18181b',
      rgb: 'rgb(24,24,27)',
      hsl: 'hsl(240,5.9%,10%)',
    },
    {
      scale: 950,
      hex: '#09090b',
      rgb: 'rgb(9,9,11)',
      hsl: 'hsl(240,10%,3.9%)',
    },
  ],
  neutral: [
    {
      scale: 50,
      hex: '#fafafa',
      rgb: 'rgb(250,250,250)',
      hsl: 'hsl(0,0%,98%)',
    },
    {
      scale: 100,
      hex: '#f5f5f5',
      rgb: 'rgb(245,245,245)',
      hsl: 'hsl(0,0%,96.1%)',
    },
    {
      scale: 200,
      hex: '#e5e5e5',
      rgb: 'rgb(229,229,229)',
      hsl: 'hsl(0,0%,89.8%)',
    },
    {
      scale: 300,
      hex: '#d4d4d4',
      rgb: 'rgb(212,212,212)',
      hsl: 'hsl(0,0%,83.1%)',
    },
    {
      scale: 400,
      hex: '#a3a3a3',
      rgb: 'rgb(163,163,163)',
      hsl: 'hsl(0,0%,63.9%)',
    },
    {
      scale: 500,
      hex: '#737373',
      rgb: 'rgb(115,115,115)',
      hsl: 'hsl(0,0%,45.1%)',
    },
    {
      scale: 600,
      hex: '#525252',
      rgb: 'rgb(82,82,82)',
      hsl: 'hsl(0,0%,32.2%)',
    },
    {
      scale: 700,
      hex: '#404040',
      rgb: 'rgb(64,64,64)',
      hsl: 'hsl(0,0%,25.1%)',
    },
    {
      scale: 800,
      hex: '#262626',
      rgb: 'rgb(38,38,38)',
      hsl: 'hsl(0,0%,14.9%)',
    },
    {
      scale: 900,
      hex: '#171717',
      rgb: 'rgb(23,23,23)',
      hsl: 'hsl(0,0%,9%)',
    },
    {
      scale: 950,
      hex: '#0a0a0a',
      rgb: 'rgb(10,10,10)',
      hsl: 'hsl(0,0%,3.9%)',
    },
  ],
  stone: [
    {
      scale: 50,
      hex: '#fafaf9',
      rgb: 'rgb(250,250,249)',
      hsl: 'hsl(60,9.1%,97.8%)',
    },
    {
      scale: 100,
      hex: '#f5f5f4',
      rgb: 'rgb(245,245,244)',
      hsl: 'hsl(60,4.8%,95.9%)',
    },
    {
      scale: 200,
      hex: '#e7e5e4',
      rgb: 'rgb(231,229,228)',
      hsl: 'hsl(20,5.9%,90%)',
    },
    {
      scale: 300,
      hex: '#d6d3d1',
      rgb: 'rgb(214,211,209)',
      hsl: 'hsl(24,5.7%,82.9%)',
    },
    {
      scale: 400,
      hex: '#a8a29e',
      rgb: 'rgb(168,162,158)',
      hsl: 'hsl(24,5.4%,63.9%)',
    },
    {
      scale: 500,
      hex: '#78716c',
      rgb: 'rgb(120,113,108)',
      hsl: 'hsl(25,5.3%,44.7%)',
    },
    {
      scale: 600,
      hex: '#57534e',
      rgb: 'rgb(87,83,78)',
      hsl: 'hsl(33.3,5.5%,32.4%)',
    },
    {
      scale: 700,
      hex: '#44403c',
      rgb: 'rgb(68,64,60)',
      hsl: 'hsl(30,6.3%,25.1%)',
    },
    {
      scale: 800,
      hex: '#292524',
      rgb: 'rgb(41,37,36)',
      hsl: 'hsl(12,6.5%,15.1%)',
    },
    {
      scale: 900,
      hex: '#1c1917',
      rgb: 'rgb(28,25,23)',
      hsl: 'hsl(24,9.8%,10%)',
    },
    {
      scale: 950,
      hex: '#0c0a09',
      rgb: 'rgb(12,10,9)',
      hsl: 'hsl(20,14.3%,4.1%)',
    },
  ],
  red: [
    {
      scale: 50,
      hex: '#fef2f2',
      rgb: 'rgb(254,242,242)',
      hsl: 'hsl(0,85.7%,97.3%)',
    },
    {
      scale: 100,
      hex: '#fee2e2',
      rgb: 'rgb(254,226,226)',
      hsl: 'hsl(0,93.3%,94.1%)',
    },
    {
      scale: 200,
      hex: '#fecaca',
      rgb: 'rgb(254,202,202)',
      hsl: 'hsl(0,96.3%,89.4%)',
    },
    {
      scale: 300,
      hex: '#fca5a5',
      rgb: 'rgb(252,165,165)',
      hsl: 'hsl(0,93.5%,81.8%)',
    },
    {
      scale: 400,
      hex: '#f87171',
      rgb: 'rgb(248,113,113)',
      hsl: 'hsl(0,90.6%,70.8%)',
    },
    {
      scale: 500,
      hex: '#ef4444',
      rgb: 'rgb(239,68,68)',
      hsl: 'hsl(0,84.2%,60.2%)',
    },
    {
      scale: 600,
      hex: '#dc2626',
      rgb: 'rgb(220,38,38)',
      hsl: 'hsl(0,72.2%,50.6%)',
    },
    {
      scale: 700,
      hex: '#b91c1c',
      rgb: 'rgb(185,28,28)',
      hsl: 'hsl(0,73.7%,41.8%)',
    },
    {
      scale: 800,
      hex: '#991b1b',
      rgb: 'rgb(153,27,27)',
      hsl: 'hsl(0,70%,35.3%)',
    },
    {
      scale: 900,
      hex: '#7f1d1d',
      rgb: 'rgb(127,29,29)',
      hsl: 'hsl(0,62.8%,30.6%)',
    },
    {
      scale: 950,
      hex: '#450a0a',
      rgb: 'rgb(69,10,10)',
      hsl: 'hsl(0,74.7%,15.5%)',
    },
  ],
  orange: [
    {
      scale: 50,
      hex: '#fff7ed',
      rgb: 'rgb(255,247,237)',
      hsl: 'hsl(33.3,100%,96.5%)',
    },
    {
      scale: 100,
      hex: '#ffedd5',
      rgb: 'rgb(255,237,213)',
      hsl: 'hsl(34.3,100%,91.8%)',
    },
    {
      scale: 200,
      hex: '#fed7aa',
      rgb: 'rgb(254,215,170)',
      hsl: 'hsl(32.1,97.7%,83.1%)',
    },
    {
      scale: 300,
      hex: '#fdba74',
      rgb: 'rgb(253,186,116)',
      hsl: 'hsl(30.7,97.2%,72.4%)',
    },
    {
      scale: 400,
      hex: '#fb923c',
      rgb: 'rgb(251,146,60)',
      hsl: 'hsl(27,96%,61%)',
    },
    {
      scale: 500,
      hex: '#f97316',
      rgb: 'rgb(249,115,22)',
      hsl: 'hsl(24.6,95%,53.1%)',
    },
    {
      scale: 600,
      hex: '#ea580c',
      rgb: 'rgb(234,88,12)',
      hsl: 'hsl(20.5,90.2%,48.2%)',
    },
    {
      scale: 700,
      hex: '#c2410c',
      rgb: 'rgb(194,65,12)',
      hsl: 'hsl(17.5,88.3%,40.4%)',
    },
    {
      scale: 800,
      hex: '#9a3412',
      rgb: 'rgb(154,52,18)',
      hsl: 'hsl(15,79.1%,33.7%)',
    },
    {
      scale: 900,
      hex: '#7c2d12',
      rgb: 'rgb(124,45,18)',
      hsl: 'hsl(15.3,74.6%,27.8%)',
    },
    {
      scale: 950,
      hex: '#431407',
      rgb: 'rgb(67,20,7)',
      hsl: 'hsl(13,81.1%,14.5%)',
    },
  ],
  amber: [
    {
      scale: 50,
      hex: '#fffbeb',
      rgb: 'rgb(255,251,235)',
      hsl: 'hsl(48,100%,96.1%)',
    },
    {
      scale: 100,
      hex: '#fef3c7',
      rgb: 'rgb(254,243,199)',
      hsl: 'hsl(48,96.5%,88.8%)',
    },
    {
      scale: 200,
      hex: '#fde68a',
      rgb: 'rgb(253,230,138)',
      hsl: 'hsl(48,96.6%,76.7%)',
    },
    {
      scale: 300,
      hex: '#fcd34d',
      rgb: 'rgb(252,211,77)',
      hsl: 'hsl(45.9,96.7%,64.5%)',
    },
    {
      scale: 400,
      hex: '#fbbf24',
      rgb: 'rgb(251,191,36)',
      hsl: 'hsl(43.3,96.4%,56.3%)',
    },
    {
      scale: 500,
      hex: '#f59e0b',
      rgb: 'rgb(245,158,11)',
      hsl: 'hsl(37.7,92.1%,50.2%)',
    },
    {
      scale: 600,
      hex: '#d97706',
      rgb: 'rgb(217,119,6)',
      hsl: 'hsl(32.1,94.6%,43.7%)',
    },
    {
      scale: 700,
      hex: '#b45309',
      rgb: 'rgb(180,83,9)',
      hsl: 'hsl(26,90.5%,37.1%)',
    },
    {
      scale: 800,
      hex: '#92400e',
      rgb: 'rgb(146,64,14)',
      hsl: 'hsl(22.7,82.5%,31.4%)',
    },
    {
      scale: 900,
      hex: '#78350f',
      rgb: 'rgb(120,53,15)',
      hsl: 'hsl(21.7,77.8%,26.5%)',
    },
    {
      scale: 950,
      hex: '#451a03',
      rgb: 'rgb(69,26,3)',
      hsl: 'hsl(20.9,91.7%,14.1%)',
    },
  ],
  yellow: [
    {
      scale: 50,
      hex: '#fefce8',
      rgb: 'rgb(254,252,232)',
      hsl: 'hsl(54.5,91.7%,95.3%)',
    },
    {
      scale: 100,
      hex: '#fef9c3',
      rgb: 'rgb(254,249,195)',
      hsl: 'hsl(54.9,96.7%,88%)',
    },
    {
      scale: 200,
      hex: '#fef08a',
      rgb: 'rgb(254,240,138)',
      hsl: 'hsl(52.8,98.3%,76.9%)',
    },
    {
      scale: 300,
      hex: '#fde047',
      rgb: 'rgb(253,224,71)',
      hsl: 'hsl(50.4,97.8%,63.5%)',
    },
    {
      scale: 400,
      hex: '#facc15',
      rgb: 'rgb(250,204,21)',
      hsl: 'hsl(47.9,95.8%,53.1%)',
    },
    {
      scale: 500,
      hex: '#eab308',
      rgb: 'rgb(234,179,8)',
      hsl: 'hsl(45.4,93.4%,47.5%)',
    },
    {
      scale: 600,
      hex: '#ca8a04',
      rgb: 'rgb(202,138,4)',
      hsl: 'hsl(40.6,96.1%,40.4%)',
    },
    {
      scale: 700,
      hex: '#a16207',
      rgb: 'rgb(161,98,7)',
      hsl: 'hsl(35.5,91.7%,32.9%)',
    },
    {
      scale: 800,
      hex: '#854d0e',
      rgb: 'rgb(133,77,14)',
      hsl: 'hsl(31.8,81%,28.8%)',
    },
    {
      scale: 900,
      hex: '#713f12',
      rgb: 'rgb(113,63,18)',
      hsl: 'hsl(28.4,72.5%,25.7%)',
    },
    {
      scale: 950,
      hex: '#422006',
      rgb: 'rgb(66,32,6)',
      hsl: 'hsl(26,83.3%,14.1%)',
    },
  ],
  lime: [
    {
      scale: 50,
      hex: '#f7fee7',
      rgb: 'rgb(247,254,231)',
      hsl: 'hsl(78.3,92%,95.1%)',
    },
    {
      scale: 100,
      hex: '#ecfccb',
      rgb: 'rgb(236,252,203)',
      hsl: 'hsl(79.6,89.1%,89.2%)',
    },
    {
      scale: 200,
      hex: '#d9f99d',
      rgb: 'rgb(217,249,157)',
      hsl: 'hsl(80.9,88.5%,79.6%)',
    },
    {
      scale: 300,
      hex: '#bef264',
      rgb: 'rgb(190,242,100)',
      hsl: 'hsl(82,84.5%,67.1%)',
    },
    {
      scale: 400,
      hex: '#a3e635',
      rgb: 'rgb(163,230,53)',
      hsl: 'hsl(82.7,78%,55.5%)',
    },
    {
      scale: 500,
      hex: '#84cc16',
      rgb: 'rgb(132,204,22)',
      hsl: 'hsl(83.7,80.5%,44.3%)',
    },
    {
      scale: 600,
      hex: '#65a30d',
      rgb: 'rgb(101,163,13)',
      hsl: 'hsl(84.8,85.2%,34.5%)',
    },
    {
      scale: 700,
      hex: '#4d7c0f',
      rgb: 'rgb(77,124,15)',
      hsl: 'hsl(85.9,78.4%,27.3%)',
    },
    {
      scale: 800,
      hex: '#3f6212',
      rgb: 'rgb(63,98,18)',
      hsl: 'hsl(86.3,69%,22.7%)',
    },
    {
      scale: 900,
      hex: '#365314',
      rgb: 'rgb(54,83,20)',
      hsl: 'hsl(87.6,61.2%,20.2%)',
    },
    {
      scale: 950,
      hex: '#1a2e05',
      rgb: 'rgb(26,46,5)',
      hsl: 'hsl(89.3,80.4%,10%)',
    },
  ],
  green: [
    {
      scale: 50,
      hex: '#f0fdf4',
      rgb: 'rgb(240,253,244)',
      hsl: 'hsl(138.5,76.5%,96.7%)',
    },
    {
      scale: 100,
      hex: '#dcfce7',
      rgb: 'rgb(220,252,231)',
      hsl: 'hsl(140.6,84.2%,92.5%)',
    },
    {
      scale: 200,
      hex: '#bbf7d0',
      rgb: 'rgb(187,247,208)',
      hsl: 'hsl(141,78.9%,85.1%)',
    },
    {
      scale: 300,
      hex: '#86efac',
      rgb: 'rgb(134,239,172)',
      hsl: 'hsl(141.7,76.6%,73.1%)',
    },
    {
      scale: 400,
      hex: '#4ade80',
      rgb: 'rgb(74,222,128)',
      hsl: 'hsl(141.9,69.2%,58%)',
    },
    {
      scale: 500,
      hex: '#22c55e',
      rgb: 'rgb(34,197,94)',
      hsl: 'hsl(142.1,70.6%,45.3%)',
    },
    {
      scale: 600,
      hex: '#16a34a',
      rgb: 'rgb(22,163,74)',
      hsl: 'hsl(142.1,76.2%,36.3%)',
    },
    {
      scale: 700,
      hex: '#15803d',
      rgb: 'rgb(21,128,61)',
      hsl: 'hsl(142.4,71.8%,29.2%)',
    },
    {
      scale: 800,
      hex: '#166534',
      rgb: 'rgb(22,101,52)',
      hsl: 'hsl(142.8,64.2%,24.1%)',
    },
    {
      scale: 900,
      hex: '#14532d',
      rgb: 'rgb(20,83,45)',
      hsl: 'hsl(143.8,61.2%,20.2%)',
    },
    {
      scale: 950,
      hex: '#052e16',
      rgb: 'rgb(5,46,22)',
      hsl: 'hsl(144.9,80.4%,10%)',
    },
  ],
  emerald: [
    {
      scale: 50,
      hex: '#ecfdf5',
      rgb: 'rgb(236,253,245)',
      hsl: 'hsl(151.8,81%,95.9%)',
    },
    {
      scale: 100,
      hex: '#d1fae5',
      rgb: 'rgb(209,250,229)',
      hsl: 'hsl(149.3,80.4%,90%)',
    },
    {
      scale: 200,
      hex: '#a7f3d0',
      rgb: 'rgb(167,243,208)',
      hsl: 'hsl(152.4,76%,80.4%)',
    },
    {
      scale: 300,
      hex: '#6ee7b7',
      rgb: 'rgb(110,231,183)',
      hsl: 'hsl(156.2,71.6%,66.9%)',
    },
    {
      scale: 400,
      hex: '#34d399',
      rgb: 'rgb(52,211,153)',
      hsl: 'hsl(158.1,64.4%,51.6%)',
    },
    {
      scale: 500,
      hex: '#10b981',
      rgb: 'rgb(16,185,129)',
      hsl: 'hsl(160.1,84.1%,39.4%)',
    },
    {
      scale: 600,
      hex: '#059669',
      rgb: 'rgb(5,150,105)',
      hsl: 'hsl(161.4,93.5%,30.4%)',
    },
    {
      scale: 700,
      hex: '#047857',
      rgb: 'rgb(4,120,87)',
      hsl: 'hsl(162.9,93.5%,24.3%)',
    },
    {
      scale: 800,
      hex: '#065f46',
      rgb: 'rgb(6,95,70)',
      hsl: 'hsl(163.1,88.1%,19.8%)',
    },
    {
      scale: 900,
      hex: '#064e3b',
      rgb: 'rgb(6,78,59)',
      hsl: 'hsl(164.2,85.7%,16.5%)',
    },
    {
      scale: 950,
      hex: '#022c22',
      rgb: 'rgb(2,44,34)',
      hsl: 'hsl(165.7,91.3%,9%)',
    },
  ],
  teal: [
    {
      scale: 50,
      hex: '#f0fdfa',
      rgb: 'rgb(240,253,250)',
      hsl: 'hsl(166.2,76.5%,96.7%)',
    },
    {
      scale: 100,
      hex: '#ccfbf1',
      rgb: 'rgb(204,251,241)',
      hsl: 'hsl(167.2,85.5%,89.2%)',
    },
    {
      scale: 200,
      hex: '#99f6e4',
      rgb: 'rgb(153,246,228)',
      hsl: 'hsl(168.4,83.8%,78.2%)',
    },
    {
      scale: 300,
      hex: '#5eead4',
      rgb: 'rgb(94,234,212)',
      hsl: 'hsl(170.6,76.9%,64.3%)',
    },
    {
      scale: 400,
      hex: '#2dd4bf',
      rgb: 'rgb(45,212,191)',
      hsl: 'hsl(172.5,66%,50.4%)',
    },
    {
      scale: 500,
      hex: '#14b8a6',
      rgb: 'rgb(20,184,166)',
      hsl: 'hsl(173.4,80.4%,40%)',
    },
    {
      scale: 600,
      hex: '#0d9488',
      rgb: 'rgb(13,148,136)',
      hsl: 'hsl(174.7,83.9%,31.6%)',
    },
    {
      scale: 700,
      hex: '#0f766e',
      rgb: 'rgb(15,118,110)',
      hsl: 'hsl(175.3,77.4%,26.1%)',
    },
    {
      scale: 800,
      hex: '#115e59',
      rgb: 'rgb(17,94,89)',
      hsl: 'hsl(176.1,69.4%,21.8%)',
    },
    {
      scale: 900,
      hex: '#134e4a',
      rgb: 'rgb(19,78,74)',
      hsl: 'hsl(175.9,60.8%,19%)',
    },
    {
      scale: 950,
      hex: '#042f2e',
      rgb: 'rgb(4,47,46)',
      hsl: 'hsl(178.6,84.3%,10%)',
    },
  ],
  cyan: [
    {
      scale: 50,
      hex: '#ecfeff',
      rgb: 'rgb(236,254,255)',
      hsl: 'hsl(183.2,100%,96.3%)',
    },
    {
      scale: 100,
      hex: '#cffafe',
      rgb: 'rgb(207,250,254)',
      hsl: 'hsl(185.1,95.9%,90.4%)',
    },
    {
      scale: 200,
      hex: '#a5f3fc',
      rgb: 'rgb(165,243,252)',
      hsl: 'hsl(186.2,93.5%,81.8%)',
    },
    {
      scale: 300,
      hex: '#67e8f9',
      rgb: 'rgb(103,232,249)',
      hsl: 'hsl(187,92.4%,69%)',
    },
    {
      scale: 400,
      hex: '#22d3ee',
      rgb: 'rgb(34,211,238)',
      hsl: 'hsl(187.9,85.7%,53.3%)',
    },
    {
      scale: 500,
      hex: '#06b6d4',
      rgb: 'rgb(6,182,212)',
      hsl: 'hsl(188.7,94.5%,42.7%)',
    },
    {
      scale: 600,
      hex: '#0891b2',
      rgb: 'rgb(8,145,178)',
      hsl: 'hsl(191.6,91.4%,36.5%)',
    },
    {
      scale: 700,
      hex: '#0e7490',
      rgb: 'rgb(14,116,144)',
      hsl: 'hsl(192.9,82.3%,31%)',
    },
    {
      scale: 800,
      hex: '#155e75',
      rgb: 'rgb(21,94,117)',
      hsl: 'hsl(194.4,69.6%,27.1%)',
    },
    {
      scale: 900,
      hex: '#164e63',
      rgb: 'rgb(22,78,99)',
      hsl: 'hsl(196.4,63.6%,23.7%)',
    },
    {
      scale: 950,
      hex: '#083344',
      rgb: 'rgb(8,51,68)',
      hsl: 'hsl(197,78.9%,14.9%)',
    },
  ],
  sky: [
    {
      scale: 50,
      hex: '#f0f9ff',
      rgb: 'rgb(240,249,255)',
      hsl: 'hsl(204,100%,97.1%)',
    },
    {
      scale: 100,
      hex: '#e0f2fe',
      rgb: 'rgb(224,242,254)',
      hsl: 'hsl(204,93.8%,93.7%)',
    },
    {
      scale: 200,
      hex: '#bae6fd',
      rgb: 'rgb(186,230,253)',
      hsl: 'hsl(200.6,94.4%,86.1%)',
    },
    {
      scale: 300,
      hex: '#7dd3fc',
      rgb: 'rgb(125,211,252)',
      hsl: 'hsl(199.4,95.5%,73.9%)',
    },
    {
      scale: 400,
      hex: '#38bdf8',
      rgb: 'rgb(56,189,248)',
      hsl: 'hsl(198.4,93.2%,59.6%)',
    },
    {
      scale: 500,
      hex: '#0ea5e9',
      rgb: 'rgb(14,165,233)',
      hsl: 'hsl(198.6,88.7%,48.4%)',
    },
    {
      scale: 600,
      hex: '#0284c7',
      rgb: 'rgb(2,132,199)',
      hsl: 'hsl(200.4,98%,39.4%)',
    },
    {
      scale: 700,
      hex: '#0369a1',
      rgb: 'rgb(3,105,161)',
      hsl: 'hsl(201.3,96.3%,32.2%)',
    },
    {
      scale: 800,
      hex: '#075985',
      rgb: 'rgb(7,89,133)',
      hsl: 'hsl(201,90%,27.5%)',
    },
    {
      scale: 900,
      hex: '#0c4a6e',
      rgb: 'rgb(12,74,110)',
      hsl: 'hsl(202,80.3%,23.9%)',
    },
    {
      scale: 950,
      hex: '#082f49',
      rgb: 'rgb(8,47,73)',
      hsl: 'hsl(204,80.2%,15.9%)',
    },
  ],
  blue: [
    {
      scale: 50,
      hex: '#eff6ff',
      rgb: 'rgb(239,246,255)',
      hsl: 'hsl(213.8,100%,96.9%)',
    },
    {
      scale: 100,
      hex: '#dbeafe',
      rgb: 'rgb(219,234,254)',
      hsl: 'hsl(214.3,94.6%,92.7%)',
    },
    {
      scale: 200,
      hex: '#bfdbfe',
      rgb: 'rgb(191,219,254)',
      hsl: 'hsl(213.3,96.9%,87.3%)',
    },
    {
      scale: 300,
      hex: '#93c5fd',
      rgb: 'rgb(147,197,253)',
      hsl: 'hsl(211.7,96.4%,78.4%)',
    },
    {
      scale: 400,
      hex: '#60a5fa',
      rgb: 'rgb(96,165,250)',
      hsl: 'hsl(213.1,93.9%,67.8%)',
    },
    {
      scale: 500,
      hex: '#3b82f6',
      rgb: 'rgb(59,130,246)',
      hsl: 'hsl(217.2,91.2%,59.8%)',
    },
    {
      scale: 600,
      hex: '#2563eb',
      rgb: 'rgb(37,99,235)',
      hsl: 'hsl(221.2,83.2%,53.3%)',
    },
    {
      scale: 700,
      hex: '#1d4ed8',
      rgb: 'rgb(29,78,216)',
      hsl: 'hsl(224.3,76.3%,48%)',
    },
    {
      scale: 800,
      hex: '#1e40af',
      rgb: 'rgb(30,64,175)',
      hsl: 'hsl(225.9,70.7%,40.2%)',
    },
    {
      scale: 900,
      hex: '#1e3a8a',
      rgb: 'rgb(30,58,138)',
      hsl: 'hsl(224.4,64.3%,32.9%)',
    },
    {
      scale: 950,
      hex: '#172554',
      rgb: 'rgb(23,37,84)',
      hsl: 'hsl(226.2,57%,21%)',
    },
  ],
  indigo: [
    {
      scale: 50,
      hex: '#eef2ff',
      rgb: 'rgb(238,242,255)',
      hsl: 'hsl(225.9,100%,96.7%)',
    },
    {
      scale: 100,
      hex: '#e0e7ff',
      rgb: 'rgb(224,231,255)',
      hsl: 'hsl(226.5,100%,93.9%)',
    },
    {
      scale: 200,
      hex: '#c7d2fe',
      rgb: 'rgb(199,210,254)',
      hsl: 'hsl(228,96.5%,88.8%)',
    },
    {
      scale: 300,
      hex: '#a5b4fc',
      rgb: 'rgb(165,180,252)',
      hsl: 'hsl(229.7,93.5%,81.8%)',
    },
    {
      scale: 400,
      hex: '#818cf8',
      rgb: 'rgb(129,140,248)',
      hsl: 'hsl(234.5,89.5%,73.9%)',
    },
    {
      scale: 500,
      hex: '#6366f1',
      rgb: 'rgb(99,102,241)',
      hsl: 'hsl(238.7,83.5%,66.7%)',
    },
    {
      scale: 600,
      hex: '#4f46e5',
      rgb: 'rgb(79,70,229)',
      hsl: 'hsl(243.4,75.4%,58.6%)',
    },
    {
      scale: 700,
      hex: '#4338ca',
      rgb: 'rgb(67,56,202)',
      hsl: 'hsl(244.5,57.9%,50.6%)',
    },
    {
      scale: 800,
      hex: '#3730a3',
      rgb: 'rgb(55,48,163)',
      hsl: 'hsl(243.7,54.5%,41.4%)',
    },
    {
      scale: 900,
      hex: '#312e81',
      rgb: 'rgb(49,46,129)',
      hsl: 'hsl(242.2,47.4%,34.3%)',
    },
    {
      scale: 950,
      hex: '#1e1b4b',
      rgb: 'rgb(30,27,75)',
      hsl: 'hsl(243.8,47.1%,20%)',
    },
  ],
  violet: [
    {
      scale: 50,
      hex: '#f5f3ff',
      rgb: 'rgb(245,243,255)',
      hsl: 'hsl(250,100%,97.6%)',
    },
    {
      scale: 100,
      hex: '#ede9fe',
      rgb: 'rgb(237,233,254)',
      hsl: 'hsl(251.4,91.3%,95.5%)',
    },
    {
      scale: 200,
      hex: '#ddd6fe',
      rgb: 'rgb(221,214,254)',
      hsl: 'hsl(250.5,95.2%,91.8%)',
    },
    {
      scale: 300,
      hex: '#c4b5fd',
      rgb: 'rgb(196,181,253)',
      hsl: 'hsl(252.5,94.7%,85.1%)',
    },
    {
      scale: 400,
      hex: '#a78bfa',
      rgb: 'rgb(167,139,250)',
      hsl: 'hsl(255.1,91.7%,76.3%)',
    },
    {
      scale: 500,
      hex: '#8b5cf6',
      rgb: 'rgb(139,92,246)',
      hsl: 'hsl(258.3,89.5%,66.3%)',
    },
    {
      scale: 600,
      hex: '#7c3aed',
      rgb: 'rgb(124,58,237)',
      hsl: 'hsl(262.1,83.3%,57.8%)',
    },
    {
      scale: 700,
      hex: '#6d28d9',
      rgb: 'rgb(109,40,217)',
      hsl: 'hsl(263.4,70%,50.4%)',
    },
    {
      scale: 800,
      hex: '#5b21b6',
      rgb: 'rgb(91,33,182)',
      hsl: 'hsl(263.4,69.3%,42.2%)',
    },
    {
      scale: 900,
      hex: '#4c1d95',
      rgb: 'rgb(76,29,149)',
      hsl: 'hsl(263.5,67.4%,34.9%)',
    },
    {
      scale: 950,
      hex: '#1e1b4b',
      rgb: 'rgb(46,16,101)',
      hsl: 'hsl(261.2,72.6%,22.9%)',
    },
  ],
  purple: [
    {
      scale: 50,
      hex: '#faf5ff',
      rgb: 'rgb(250,245,255)',
      hsl: 'hsl(270,100%,98%)',
    },
    {
      scale: 100,
      hex: '#f3e8ff',
      rgb: 'rgb(243,232,255)',
      hsl: 'hsl(268.7,100%,95.5%)',
    },
    {
      scale: 200,
      hex: '#e9d5ff',
      rgb: 'rgb(233,213,255)',
      hsl: 'hsl(268.6,100%,91.8%)',
    },
    {
      scale: 300,
      hex: '#d8b4fe',
      rgb: 'rgb(216,180,254)',
      hsl: 'hsl(269.2,97.4%,85.1%)',
    },
    {
      scale: 400,
      hex: '#c084fc',
      rgb: 'rgb(192,132,252)',
      hsl: 'hsl(270,95.2%,75.3%)',
    },
    {
      scale: 500,
      hex: '#a855f7',
      rgb: 'rgb(168,85,247)',
      hsl: 'hsl(270.7,91%,65.1%)',
    },
    {
      scale: 600,
      hex: '#9333ea',
      rgb: 'rgb(147,51,234)',
      hsl: 'hsl(271.5,81.3%,55.9%)',
    },
    {
      scale: 700,
      hex: '#7e22ce',
      rgb: 'rgb(126,34,206)',
      hsl: 'hsl(272.1,71.7%,47.1%)',
    },
    {
      scale: 800,
      hex: '#6b21a8',
      rgb: 'rgb(107,33,168)',
      hsl: 'hsl(272.9,67.2%,39.4%)',
    },
    {
      scale: 900,
      hex: '#581c87',
      rgb: 'rgb(88,28,135)',
      hsl: 'hsl(273.6,65.6%,32%)',
    },
    {
      scale: 950,
      hex: '#3b0764',
      rgb: 'rgb(59,7,100)',
      hsl: 'hsl(273.5,86.9%,21%)',
    },
  ],
  fuchsia: [
    {
      scale: 50,
      hex: '#fdf4ff',
      rgb: 'rgb(253,244,255)',
      hsl: 'hsl(289.1,100%,97.8%)',
    },
    {
      scale: 100,
      hex: '#fae8ff',
      rgb: 'rgb(250,232,255)',
      hsl: 'hsl(287,100%,95.5%)',
    },
    {
      scale: 200,
      hex: '#f5d0fe',
      rgb: 'rgb(245,208,254)',
      hsl: 'hsl(288.3,95.8%,90.6%)',
    },
    {
      scale: 300,
      hex: '#f0abfc',
      rgb: 'rgb(240,171,252)',
      hsl: 'hsl(291.1,93.1%,82.9%)',
    },
    {
      scale: 400,
      hex: '#e879f9',
      rgb: 'rgb(232,121,249)',
      hsl: 'hsl(292,91.4%,72.5%)',
    },
    {
      scale: 500,
      hex: '#d946ef',
      rgb: 'rgb(217,70,239)',
      hsl: 'hsl(292.2,84.1%,60.6%)',
    },
    {
      scale: 600,
      hex: '#c026d3',
      rgb: 'rgb(192,38,211)',
      hsl: 'hsl(293.4,69.5%,48.8%)',
    },
    {
      scale: 700,
      hex: '#a21caf',
      rgb: 'rgb(162,28,175)',
      hsl: 'hsl(294.7,72.4%,39.8%)',
    },
    {
      scale: 800,
      hex: '#86198f',
      rgb: 'rgb(134,25,143)',
      hsl: 'hsl(295.4,70.2%,32.9%)',
    },
    {
      scale: 900,
      hex: '#701a75',
      rgb: 'rgb(112,26,117)',
      hsl: 'hsl(296.7,63.6%,28%)',
    },
    {
      scale: 950,
      hex: '#4a044e',
      rgb: 'rgb(74,4,78)',
      hsl: 'hsl(296.8,90.2%,16.1%)',
    },
  ],
  pink: [
    {
      scale: 50,
      hex: '#fdf2f8',
      rgb: 'rgb(253,242,248)',
      hsl: 'hsl(327.3,73.3%,97.1%)',
    },
    {
      scale: 100,
      hex: '#fce7f3',
      rgb: 'rgb(252,231,243)',
      hsl: 'hsl(325.7,77.8%,94.7%)',
    },
    {
      scale: 200,
      hex: '#fbcfe8',
      rgb: 'rgb(251,207,232)',
      hsl: 'hsl(325.9,84.6%,89.8%)',
    },
    {
      scale: 300,
      hex: '#f9a8d4',
      rgb: 'rgb(249,168,212)',
      hsl: 'hsl(327.4,87.1%,81.8%)',
    },
    {
      scale: 400,
      hex: '#f472b6',
      rgb: 'rgb(244,114,182)',
      hsl: 'hsl(328.6,85.5%,70.2%)',
    },
    {
      scale: 500,
      hex: '#ec4899',
      rgb: 'rgb(236,72,153)',
      hsl: 'hsl(330.4,81.2%,60.4%)',
    },
    {
      scale: 600,
      hex: '#db2777',
      rgb: 'rgb(219,39,119)',
      hsl: 'hsl(333.3,71.4%,50.6%)',
    },
    {
      scale: 700,
      hex: '#be185d',
      rgb: 'rgb(190,24,93)',
      hsl: 'hsl(335.1,77.6%,42%)',
    },
    {
      scale: 800,
      hex: '#9d174d',
      rgb: 'rgb(157,23,77)',
      hsl: 'hsl(335.8,74.4%,35.3%)',
    },
    {
      scale: 900,
      hex: '#831843',
      rgb: 'rgb(131,24,67)',
      hsl: 'hsl(335.9,69%,30.4%)',
    },
    {
      scale: 950,
      hex: '#500724',
      rgb: 'rgb(80,7,36)',
      hsl: 'hsl(336.2,83.9%,17.1%)',
    },
  ],
  rose: [
    {
      scale: 50,
      hex: '#fff1f2',
      rgb: 'rgb(255,241,242)',
      hsl: 'hsl(355.7,100%,97.3%)',
    },
    {
      scale: 100,
      hex: '#ffe4e6',
      rgb: 'rgb(255,228,230)',
      hsl: 'hsl(355.6,100%,94.7%)',
    },
    {
      scale: 200,
      hex: '#fecdd3',
      rgb: 'rgb(254,205,211)',
      hsl: 'hsl(352.7,96.1%,90%)',
    },
    {
      scale: 300,
      hex: '#fda4af',
      rgb: 'rgb(253,164,175)',
      hsl: 'hsl(352.6,95.7%,81.8%)',
    },
    {
      scale: 400,
      hex: '#fb7185',
      rgb: 'rgb(251,113,133)',
      hsl: 'hsl(351.3,94.5%,71.4%)',
    },
    {
      scale: 500,
      hex: '#f43f5e',
      rgb: 'rgb(244,63,94)',
      hsl: 'hsl(349.7,89.2%,60.2%)',
    },
    {
      scale: 600,
      hex: '#e11d48',
      rgb: 'rgb(225,29,72)',
      hsl: 'hsl(346.8,77.2%,49.8%)',
    },
    {
      scale: 700,
      hex: '#be123c',
      rgb: 'rgb(190,18,60)',
      hsl: 'hsl(345.3,82.7%,40.8%)',
    },
    {
      scale: 800,
      hex: '#9f1239',
      rgb: 'rgb(159,18,57)',
      hsl: 'hsl(343.4,79.7%,34.7%)',
    },
    {
      scale: 900,
      hex: '#881337',
      rgb: 'rgb(136,19,55)',
      hsl: 'hsl(341.5,75.5%,30.4%)',
    },
    {
      scale: 950,
      hex: '#4c0519',
      rgb: 'rgb(76,5,25)',
      hsl: 'hsl(343.1,87.7%,15.9%)',
    },
  ],
}

export const colorMapping = {
  light: {
    background: 'white',
    foreground: '{{base}}-950',
    card: 'white',
    'card-foreground': '{{base}}-950',
    popover: 'white',
    'popover-foreground': '{{base}}-950',
    primary: '{{base}}-900',
    'primary-foreground': '{{base}}-50',
    secondary: '{{base}}-100',
    'secondary-foreground': '{{base}}-900',
    muted: '{{base}}-100',
    'muted-foreground': '{{base}}-500',
    accent: '{{base}}-100',
    'accent-foreground': '{{base}}-900',
    destructive: 'red-500',
    'destructive-foreground': '{{base}}-50',
    border: '{{base}}-200',
    input: '{{base}}-200',
    ring: '{{base}}-950',
  },
  dark: {
    background: '{{base}}-950',
    foreground: '{{base}}-50',
    card: '{{base}}-950',
    'card-foreground': '{{base}}-50',
    popover: '{{base}}-950',
    'popover-foreground': '{{base}}-50',
    primary: '{{base}}-50',
    'primary-foreground': '{{base}}-900',
    secondary: '{{base}}-800',
    'secondary-foreground': '{{base}}-50',
    muted: '{{base}}-800',
    'muted-foreground': '{{base}}-400',
    accent: '{{base}}-800',
    'accent-foreground': '{{base}}-50',
    destructive: 'red-900',
    'destructive-foreground': '{{base}}-50',
    border: '{{base}}-800',
    input: '{{base}}-800',
    ring: '{{base}}-300',
  },
} as const


--- apps/design-system/registry/copy-writing.ts ---
import { Registry } from './schema'

export const copyWriting: Registry = [
  {
    name: 'copy-button-verbs',
    type: 'components:example',
    files: ['example/copy-button-verbs.tsx'],
    registryDependencies: ['button'],
    category: 'Getting Started',
    subcategory: 'Copywriting',
  },
  {
    name: 'copy-form-labels',
    type: 'components:example',
    files: ['example/copy-form-labels.tsx'],
    registryDependencies: ['form'],
    category: 'Getting Started',
    subcategory: 'Copywriting',
  },
  {
    name: 'copy-error-messages',
    type: 'components:example',
    files: ['example/copy-error-messages.tsx'],
    registryDependencies: ['form'],
    category: 'Getting Started',
    subcategory: 'Copywriting',
  },
  {
    name: 'copy-success-messages',
    type: 'components:example',
    files: ['example/copy-success-messages.tsx'],
    registryDependencies: ['form'],
    category: 'Getting Started',
    subcategory: 'Copywriting',
  },
  {
    name: 'copy-tooltips',
    type: 'components:example',
    files: ['example/copy-tooltips.tsx'],
    registryDependencies: ['tooltip'],
    category: 'Getting Started',
    subcategory: 'Copywriting',
  },
  {
    name: 'copy-loading-states',
    type: 'components:example',
    files: ['example/copy-loading-states.tsx'],
    registryDependencies: ['loading-state'],
    category: 'Getting Started',
    subcategory: 'Copywriting',
  },
  {
    name: 'copy-empty-states',
    type: 'components:example',
    files: ['example/copy-empty-states.tsx'],
    registryDependencies: ['empty-state'],
    category: 'Getting Started',
    subcategory: 'Copywriting',
  },
  {
    name: 'copy-confirmations',
    type: 'components:example',
    files: ['example/copy-confirmations.tsx'],
    registryDependencies: ['confirmation'],
    category: 'Getting Started',
    subcategory: 'Copywriting',
  },
]


--- apps/design-system/registry/fragments.ts ---
import { Registry } from '@/registry/schema'

export const fragments: Registry = [
  {
    name: 'ConfirmationModal',
    type: 'components:fragment',
    files: ['/Dialogs/ConfirmationModal.tsx'],
    optionalPath: '/Dialogs',
  },
  {
    name: 'EmptyStatePresentational',
    type: 'components:fragment',
    files: ['/EmptyStatePresentational/index.tsx'],
    optionalPath: '/EmptyStatePresentational',
  },
  {
    name: 'TextConfirmModal',
    type: 'components:fragment',
    files: ['/Dialogs/TextConfirmModal.tsx'],
    optionalPath: '/Dialogs',
  },
  {
    name: 'PageContainer',
    type: 'components:fragment',
    files: ['/PageContainer/index.tsx'],
    optionalPath: '/PageContainer',
  },
  {
    name: 'PageHeader',
    type: 'components:fragment',
    files: ['/PageHeader/index.tsx'],
    optionalPath: '/PageHeader',
  },
  {
    name: 'PageSection',
    type: 'components:fragment',
    files: ['/PageSection/index.tsx'],
    optionalPath: '/PageSection',
  },
]


--- apps/design-system/types/nav.ts ---
// import { Icons } from '@/components/icons'

export interface NavItem {
  title: string
  href?: string
  disabled?: boolean
  external?: boolean
  icon?: any // to do: clean up later | keyof typeof Icons
  label?: string
  priority?: boolean // If true, item appears first even when section is sorted alphabetically
}

export interface NavItemWithChildren extends NavItem {
  items: NavItemWithChildren[]
  sortOrder?: 'manual' | 'alphabetical' // Sidebar navigation sort order for top-level sections
}

export interface MainNavItem extends NavItem {}

export interface SidebarNavItem extends NavItemWithChildren {}


--- CONTRIBUTING.md ---
# CONTRIBUTING.md

Thank you for contributing to Supabase! We’re a big, exciting open source project and we’d love to have you contribute! Here’s some resources and guidance to help you get started:

[1. Getting Started](#getting-started)
[2. Issues](#issues)
[3. Pull Requests](#pull-requests)

## Getting Started

To ensure a positive and inclusive environment, please read our [code of conduct](https://github.com/supabase/.github/blob/main/CODE_OF_CONDUCT.md) before contributing. For help setting up the code in this repo, please follow our [DEVELOPERS.md](https://github.com/supabase/supabase/blob/master/DEVELOPERS.md) file. For the [docs](https://supabase.com/docs) site, follow this [CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/apps/docs/CONTRIBUTING.md) guide.

## Issues

If you find a bug, please create an Issue and we’ll triage it.

- Please search [existing Issues](https://github.com/supabase/supabase/issues) before creating a new one.
- Please include a clear description of the problem along with steps to reproduce it. Exact steps with screenshots and urls really help here.

## Pull Requests

We actively welcome your Pull Requests! A couple of things to keep in mind before you submit:

- If you’re fixing an Issue, make sure someone else hasn’t already created a PR fixing the same issue. Likewise, make sure to link your PR to the related Issue(s).
- We will always try to accept the first viable PR that resolves the Issue.
- If you're new, we encourage you to take a look at issues tagged with [good first issue](https://github.com/supabase/supabase/labels/good%20first%20issue).
- If you’re submitting a new feature, make sure you have opened a [Discussion](https://github.com/orgs/supabase/discussions/new/choose) to discuss the new feature before opening a PR. We’d love to accept your hard work, but unfortunately if a feature hasn’t gone through a proper design process, your PR will be closed.
- Please use the PR message template and provide detailed context for quicker review. PRs without clear problem statements will be closed.

Prior to submitting your PR, please conduct the following pre-flight checks:

- Run `npm run build` locally to ensure that your code builds successfully without having to wait on us to approve Vercel Preview deploys.
- Ensure that the Prettier tests run successfully on your PR.

Running these before you create the PR will help reduce back and forth with the team.


## Links discovered
- [code of conduct](https://github.com/supabase/.github/blob/main/CODE_OF_CONDUCT.md)
- [DEVELOPERS.md](https://github.com/supabase/supabase/blob/master/DEVELOPERS.md)
- [docs](https://supabase.com/docs)
- [CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/apps/docs/CONTRIBUTING.md)
- [existing Issues](https://github.com/supabase/supabase/issues)
- [good first issue](https://github.com/supabase/supabase/labels/good%20first%20issue)
- [Discussion](https://github.com/orgs/supabase/discussions/new/choose)

--- SECURITY.md ---
apps/docs/public/.well-known/security.txt

--- docker/CHANGELOG.md ---
# Changelog

All notable changes to the Supabase self-hosted Docker configuration.

Changes are grouped by service rather than by change type. See [versions.md](./versions.md) 
for complete image version history and rollback information.

Check updates for each service to learn more.

**Note:** Configuration updates marked with "requires [...] update" are already included in the latest version of the repository. Pull the latest changes or refer to the linked PR for manual updates. After updating `docker-compose.yml`, pull latest images and recreate containers - use `docker compose pull && docker compose down && docker compose up -d`.

---

## Unreleased

---

## [2025-12-18]

### Documentation
- Updated self-hosting installation and configuration guide - PR [#40901](https://github.com/supabase/supabase/pull/40901), PR [#41438](https://github.com/supabase/supabase/pull/41438)

### Utils
- Added `generate-keys.sh` - PR [#41363](https://github.com/supabase/supabase/pull/41363)
- Added `db-passwd.sh` - PR [#41432](https://github.com/supabase/supabase/pull/41432)
- Changed `reset.sh` to POSIX and added more checks - PR [#41361](https://github.com/supabase/supabase/pull/41361)

### Studio
- Updated to `2025.12.17-sha-43f4f7f`
- ⚠️ Fixed additional potential issues related to [React2Shell](https://vercel.com/kb/bulletin/react2shell)
- Fixed an issue with the Users page not being updated on changes - PR [#41254](https://github.com/supabase/supabase/pull/41254)

### MCP Server
- Updated to `v0.5.10` - [Release](https://github.com/supabase-community/supabase-mcp/releases/tag/v0.5.10)

### Auth
- Updated to `v2.184.0` - [Changelog](https://github.com/supabase/auth/blob/master/CHANGELOG.md) | [Release](https://github.com/supabase/auth/releases/tag/v2.184.0)

### Postgres Meta
- Updated to `v0.95.1` - [Release](https://github.com/supabase/postgres-meta/releases/tag/v0.95.1)

### Analytics (Logflare)
- Updated to `v1.27.0` - [Release](https://github.com/Logflare/logflare/releases/tag/v1.27.0)
- Fixed multiple issues, including a race condition

---

## [2025-12-10]

### Studio

- Updated to `2025.12.09-sha-434634f`
- ⚠️ Fixed potential issues related to [React2Shell](https://vercel.com/kb/bulletin/react2shell)

### MCP Server

- Updated to `v0.5.9` - [Release](https://github.com/supabase-community/supabase-mcp/releases/tag/v0.5.9)
- ⚠️ Changed MCP tool `get_anon_key` to `get_publishable_keys`

### PostgREST

- Updated to `v14.1` - [Changelog](https://github.com/PostgREST/postgrest/blob/main/CHANGELOG.md) | [Release](https://github.com/PostgREST/postgrest/releases/tag/v14.1)
- ⚠️ **Major upgrade from v13.x to v14.x** - please report any unexpected behavior

### Realtime

- Updated to `v2.68.0` - [Releases](https://github.com/supabase/realtime/releases/tag/v2.68.0)

### Storage

- Updated to `v1.33.0` - [Release](https://github.com/supabase/storage/releases/tag/v1.33.0)

### Edge Runtime

- Updated to `v1.69.28` - [Release](https://github.com/supabase/edge-runtime/releases/tag/v1.69.28)

### Analytics (Logflare)

- Updated to `v1.26.25` - [Release](https://github.com/Logflare/logflare/releases/tag/v1.26.25)

---

## [2025-12-08]

### Realtime
- No image update
- Changed boolean values to strings in Docker Compose for better compatibility with Podman - PR [#40994](https://github.com/supabase/supabase/pull/40994), also PR [realtime#1614](https://github.com/supabase/realtime/pull/1614)
- Changed healthcheck in Docker Compose for better compatibility with Podman - PR [#41159](https://github.com/supabase/supabase/pull/41159)

---

## [2025-11-26]

### Studio
- Updated to `2025.11.26-sha-8f096b5`
- Fixed MCP `get_advisors` tool - PR [#40783](https://github.com/supabase/supabase/pull/40783)
- Fixed AI Assistant request schema - PR [#40830](https://github.com/supabase/supabase/pull/40830)
- Fixed log drains page - PR [#40835](https://github.com/supabase/supabase/pull/40835)

### Realtime
- Updated to `v2.65.3` - [Release](https://github.com/supabase/realtime/releases/tag/v2.65.3)

### Analytics (Logflare)
- Updated to `v1.26.13` - [Release](https://github.com/Logflare/logflare/releases/tag/v1.26.13)
- Fixed crashdump when `POSTGRES_BACKEND_URL` is malformed - PR [logflare#2954](https://github.com/Logflare/logflare/pull/2954)

---

## [2025-11-25]

### Studio
- Updated to `2025.11.24-sha-d990ae8` - [Dashboard updates](https://github.com/orgs/supabase/discussions/40734)
- Fixed Queues configuration UI and added [documentation for exposed queue schema](https://supabase.com/docs/guides/queues/expose-self-hosted-queues) - PR [#40078](https://github.com/supabase/supabase/pull/40078)
- Fixed parameterized SQL queries in MCP tools - PR [#40499](https://github.com/supabase/supabase/pull/40499)
- Fixed Studio showing paid options for log drains - PR [#40510](https://github.com/supabase/supabase/pull/40510)
- Fixed AI Assistant authentication - PR [#40654](https://github.com/supabase/supabase/pull/40654)

### Auth
- Updated to `v2.183.0` - [Changelog](https://github.com/supabase/auth/blob/master/CHANGELOG.md) | [Release](https://github.com/supabase/auth/releases/tag/v2.183.0)

### Realtime
- Updated to `v2.65.2` - [Release](https://github.com/supabase/realtime/releases/tag/v2.65.2)
- Fixed handling of boolean configurations options - PR [realtime#1614](https://github.com/supabase/realtime/pull/1614)

### Storage
- Updated to `v1.32.0` - [Release](https://github.com/supabase/storage/releases/tag/v1.32.0)

### Edge Runtime
- Updated to `v1.69.25` - [Release](https://github.com/supabase/edge-runtime/releases/tag/v1.69.25)

### Analytics (Logflare)
- Updated to `v1.26.12` - [Release](https://github.com/Logflare/logflare/releases/tag/v1.26.12)
- Fixed Auth logs query - PR [logflare#2936](https://github.com/Logflare/logflare/pull/2936)
- Fixed build configuration to prevent crashes with "Illegal instruction (core dumped)" - PR [logflare#2942](https://github.com/Logflare/logflare/pull/2942)

---

## [2025-11-17]

### Storage
- No image update
- Fixed resumable uploads for files larger than 6MB (requires `docker-compose.yml` update) - PR [#40500](https://github.com/supabase/supabase/pull/40500)

---

## [2025-11-12]

### Studio
- Updated to `2025.11.10-sha-5291fe3` - [Dashboard updates](https://github.com/orgs/supabase/discussions/40083)
- Added log drains - PR [#28297](https://github.com/supabase/supabase/pull/28297)
- Fixed Studio using `postgres` role instead of `supabase_admin` - PR [#39946](https://github.com/supabase/supabase/pull/39946)

### Auth
- Updated to `v2.182.1` - [Changelog](https://github.com/supabase/auth/blob/master/CHANGELOG.md#21821-2025-11-05) | [Release](https://github.com/supabase/auth/releases/tag/v2.182.1)

### Realtime
- Updated to `v2.63.0` - [Release](https://github.com/supabase/realtime/releases/tag/v2.63.0)

### Storage
- Updated to `v1.29.0` - [Release](https://github.com/supabase/storage/releases/tag/v1.29.0)

### Edge Runtime
- Updated to `v1.69.23` - [Release](https://github.com/supabase/edge-runtime/releases/tag/v1.69.23)

### Supavisor
- Updated to `v2.7.4` - [Release](https://github.com/supabase/supavisor/releases/tag/v2.7.4)

---

## [2025-11-05]

### Studio
- No image update
- Fixed Studio failing to connect to Postgres with non-default settings (requires `docker-compose.yml` update) - PR [#40169](https://github.com/supabase/supabase/pull/40169)

### Realtime
- No image update
- Fixed realtime logs not showing in Studio (requires `volumes/logs/vector.yml` update) - PR [#39963](https://github.com/supabase/supabase/pull/39963)

---

## [2025-10-28]

### Studio
- Updated to `2025.10.27-sha-85b84e0` - [Dashboard updates](https://github.com/orgs/supabase/discussions/40083)
- Fixed broken authentication when uploading files to Storage - PR [#39829](https://github.com/supabase/supabase/pull/39829)

### Realtime
- Updated to `v2.57.2` - [Release](https://github.com/supabase/realtime/releases/tag/v2.57.2)

### Storage
- Updated to `v1.28.2` - [Release](https://github.com/supabase/storage/releases/tag/v1.28.2)

### Postgres Meta
- Updated to `v0.93.1` - [Release](https://github.com/supabase/postgres-meta/releases/tag/v0.93.1)

### Edge Runtime
- Updated to `v1.69.15` - [Release](https://github.com/supabase/edge-runtime/releases/tag/v1.69.15)

---

## [2025-10-27]

### Studio
- No image update
- Added Kong configuration for MCP server routes (requires `volumes/api/kong.yml` update) - PR [#39849](https://github.com/supabase/supabase/pull/39849)
- Added [documentation page](https://supabase.com/docs/guides/self-hosting/enable-mcp) for MCP server configuration - PR [#39952](https://github.com/supabase/supabase/pull/39952)

---

## [2025-10-21]

### Studio
- Updated to `2025.10.20-sha-5005fc6` - [Dashboard updates](https://github.com/orgs/supabase/discussions/39709)
- Fixed issues with Edge Functions and cron logs not being visible in Studio - PR [#39388](https://github.com/supabase/supabase/pull/39388), PR [#39704](https://github.com/supabase/supabase/pull/39704), PR [#39711](https://github.com/supabase/supabase/pull/39711)

### Realtime
- Updated to `v2.56.0` - [Release](https://github.com/supabase/realtime/releases/tag/v2.56.0)

### Storage
- Updated to `v1.28.1` - [Release](https://github.com/supabase/storage/releases/tag/v1.28.1)

### Postgres Meta
- Updated to `v0.93.0` - [Release](https://github.com/supabase/postgres-meta/releases/tag/v0.93.0)

### Edge Runtime
- Updated to `v1.69.14` - [Release](https://github.com/supabase/edge-runtime/releases/tag/v1.69.14)

### Supavisor
- Updated to `v2.7.3` - [Release](https://github.com/supabase/supavisor/releases/tag/v2.7.3)

---

## [2025-10-13]

### Analytics (Logflare)
- Updated to `v1.22.6` - [Release](https://github.com/Logflare/logflare/releases/tag/v1.22.6)

---

## [2025-10-08]

### Studio
- Updated to `2025.10.01-sha-8460121` - [Dashboard updates](https://github.com/orgs/supabase/discussions/39709)
- Added "local" remote MCP server - PR [#38797](https://github.com/supabase/supabase/pull/38797), PR [#39041](https://github.com/supabase/supabase/pull/39041)
- ⚠️ Changed Studio connection method to `postgres-meta` - affects non-standard database port configurations

### Auth
- Updated to `v2.180.0` - [Release](https://github.com/supabase/auth/releases/tag/v2.180.0)

### PostgREST
- Updated to `v13.0.7` - [Release](https://github.com/PostgREST/postgrest/releases/tag/v13.0.7) | [Changelog](https://github.com/PostgREST/postgrest/blob/main/CHANGELOG.md)

### Realtime
- Updated to `v2.51.11` - [Release](https://github.com/supabase/realtime/releases/tag/v2.51.11)

### Storage
- Updated to `v1.28.0` - [Release](https://github.com/supabase/storage/releases/tag/v1.28.0)

### Postgres Meta
- Updated to `v0.91.6` - [Release](https://github.com/supabase/postgres-meta/releases/tag/v0.91.6)

### Analytics (Logflare)
- Updated to `v1.22.4` - [Release](https://github.com/Logflare/logflare/releases/tag/v1.22.4)

### Postgres
- Updated to `15.8.1.085` - [Release](https://github.com/supabase/postgres/releases/tag/15.8.1.085)

### Supavisor
- Updated to `2.7.0` - [Release](https://github.com/supabase/supavisor/releases/tag/v2.7.0)

---


## Links discovered
- [versions.md](https://github.com/supabase/supabase/blob/master/docker/versions.md)
- [#40901](https://github.com/supabase/supabase/pull/40901)
- [#41438](https://github.com/supabase/supabase/pull/41438)
- [#41363](https://github.com/supabase/supabase/pull/41363)
- [#41432](https://github.com/supabase/supabase/pull/41432)
- [#41361](https://github.com/supabase/supabase/pull/41361)
- [React2Shell](https://vercel.com/kb/bulletin/react2shell)
- [#41254](https://github.com/supabase/supabase/pull/41254)
- [Release](https://github.com/supabase-community/supabase-mcp/releases/tag/v0.5.10)
- [Changelog](https://github.com/supabase/auth/blob/master/CHANGELOG.md)
- [Release](https://github.com/supabase/auth/releases/tag/v2.184.0)
- [Release](https://github.com/supabase/postgres-meta/releases/tag/v0.95.1)
- [Release](https://github.com/Logflare/logflare/releases/tag/v1.27.0)
- [Release](https://github.com/supabase-community/supabase-mcp/releases/tag/v0.5.9)
- [Changelog](https://github.com/PostgREST/postgrest/blob/main/CHANGELOG.md)
- [Release](https://github.com/PostgREST/postgrest/releases/tag/v14.1)
- [Releases](https://github.com/supabase/realtime/releases/tag/v2.68.0)
- [Release](https://github.com/supabase/storage/releases/tag/v1.33.0)
- [Release](https://github.com/supabase/edge-runtime/releases/tag/v1.69.28)
- [Release](https://github.com/Logflare/logflare/releases/tag/v1.26.25)
- [#40994](https://github.com/supabase/supabase/pull/40994)
- [realtime#1614](https://github.com/supabase/realtime/pull/1614)
- [#41159](https://github.com/supabase/supabase/pull/41159)
- [#40783](https://github.com/supabase/supabase/pull/40783)
- [#40830](https://github.com/supabase/supabase/pull/40830)
- [#40835](https://github.com/supabase/supabase/pull/40835)
- [Release](https://github.com/supabase/realtime/releases/tag/v2.65.3)
- [Release](https://github.com/Logflare/logflare/releases/tag/v1.26.13)
- [logflare#2954](https://github.com/Logflare/logflare/pull/2954)
- [Dashboard updates](https://github.com/orgs/supabase/discussions/40734)
- [documentation for exposed queue schema](https://supabase.com/docs/guides/queues/expose-self-hosted-queues)
- [#40078](https://github.com/supabase/supabase/pull/40078)
- [#40499](https://github.com/supabase/supabase/pull/40499)
- [#40510](https://github.com/supabase/supabase/pull/40510)
- [#40654](https://github.com/supabase/supabase/pull/40654)
- [Release](https://github.com/supabase/auth/releases/tag/v2.183.0)
- [Release](https://github.com/supabase/realtime/releases/tag/v2.65.2)
- [Release](https://github.com/supabase/storage/releases/tag/v1.32.0)
- [Release](https://github.com/supabase/edge-runtime/releases/tag/v1.69.25)
- [Release](https://github.com/Logflare/logflare/releases/tag/v1.26.12)
- [logflare#2936](https://github.com/Logflare/logflare/pull/2936)
- [logflare#2942](https://github.com/Logflare/logflare/pull/2942)
- [#40500](https://github.com/supabase/supabase/pull/40500)
- [Dashboard updates](https://github.com/orgs/supabase/discussions/40083)
- [#28297](https://github.com/supabase/supabase/pull/28297)
- [#39946](https://github.com/supabase/supabase/pull/39946)
- [Changelog](https://github.com/supabase/auth/blob/master/CHANGELOG.md#21821-2025-11-05)
- [Release](https://github.com/supabase/auth/releases/tag/v2.182.1)
- [Release](https://github.com/supabase/realtime/releases/tag/v2.63.0)
- [Release](https://github.com/supabase/storage/releases/tag/v1.29.0)
- [Release](https://github.com/supabase/edge-runtime/releases/tag/v1.69.23)
- [Release](https://github.com/supabase/supavisor/releases/tag/v2.7.4)
- [#40169](https://github.com/supabase/supabase/pull/40169)
- [#39963](https://github.com/supabase/supabase/pull/39963)
- [#39829](https://github.com/supabase/supabase/pull/39829)
- [Release](https://github.com/supabase/realtime/releases/tag/v2.57.2)
- [Release](https://github.com/supabase/storage/releases/tag/v1.28.2)
- [Release](https://github.com/supabase/postgres-meta/releases/tag/v0.93.1)
- [Release](https://github.com/supabase/edge-runtime/releases/tag/v1.69.15)
- [#39849](https://github.com/supabase/supabase/pull/39849)
- [documentation page](https://supabase.com/docs/guides/self-hosting/enable-mcp)
- [#39952](https://github.com/supabase/supabase/pull/39952)
- [Dashboard updates](https://github.com/orgs/supabase/discussions/39709)
- [#39388](https://github.com/supabase/supabase/pull/39388)
- [#39704](https://github.com/supabase/supabase/pull/39704)
- [#39711](https://github.com/supabase/supabase/pull/39711)
- [Release](https://github.com/supabase/realtime/releases/tag/v2.56.0)
- [Release](https://github.com/supabase/storage/releases/tag/v1.28.1)
- [Release](https://github.com/supabase/postgres-meta/releases/tag/v0.93.0)
- [Release](https://github.com/supabase/edge-runtime/releases/tag/v1.69.14)
- [Release](https://github.com/supabase/supavisor/releases/tag/v2.7.3)
- [Release](https://github.com/Logflare/logflare/releases/tag/v1.22.6)
- [#38797](https://github.com/supabase/supabase/pull/38797)
- [#39041](https://github.com/supabase/supabase/pull/39041)
- [Release](https://github.com/supabase/auth/releases/tag/v2.180.0)
- [Release](https://github.com/PostgREST/postgrest/releases/tag/v13.0.7)
- [Release](https://github.com/supabase/realtime/releases/tag/v2.51.11)
- [Release](https://github.com/supabase/storage/releases/tag/v1.28.0)
- [Release](https://github.com/supabase/postgres-meta/releases/tag/v0.91.6)
- [Release](https://github.com/Logflare/logflare/releases/tag/v1.22.4)
- [Release](https://github.com/supabase/postgres/releases/tag/15.8.1.085)
- [Release](https://github.com/supabase/supavisor/releases/tag/v2.7.0)

--- apps/www/_blog/2021-12-01-realtime-row-level-security-in-postgresql.mdx ---
---
title: 'Realtime Postgres RLS now available on Supabase'
description: 'Realtime database changes are now broadcast to authenticated users, respecting the same PostgreSQL policies that you use for Row Level Security.'
author: oli_rice
author_url: https://github.com/olirice
author_image_url: https://github.com/olirice.png
image: launch-week-three/realtime-row-level-security-in-postgresql/realtime-row-level-security-in-postgresql-og.png
thumb: launch-week-three/realtime-row-level-security-in-postgresql/realtime-row-level-security-in-postgresql-thumb.png
categories:
  - product
tags:
  - launch-week
  - realtime
  - security
date: '2021-12-01'
toc_depth: 3
video: https://www.youtube.com/v/zHvatf2wySI
---

Realtime is a server that listens to changes in your PostgreSQL database and broadcasts the changes to clients through a websocket connection.

Today, we're announcing security improvements to Realtime, where database changes will be broadcast to authenticated users, respecting the same PostgreSQL policies that you use for Row Level Security.

## Demo

<div className="video-container">
  <iframe
    className="video-with-border w-full"
    src="https://www.youtube-nocookie.com/embed/zHvatf2wySI"
    frameBorder="1"
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
    allowFullScreen
  ></iframe>
</div>

## Overview

Since the first commit of Realtime server back in [September 2019](https://github.com/supabase/realtime/commit/175f649784147af80acfc9ff5be9d160285c76ea),
we've worked hard to improve its usability and scalability.

Until now, Realtime did not adhere to RLS policies and instead broadcast all database changes to all clients.
The unsafe nature of this behavior is the reason why Realtime has been an opt-in feature, and a key reason why we are still in Beta.

As more developers rely on Realtime to receive and send database changes in their apps and services,
security has become a primary concern for us and others in the community who wish to build secure systems with Realtime.

Supabase projects have supported Row Level Security (RLS) for API authorization since our [Auth launch](https://supabase.io/blog/supabase-auth).
In that time, it has quickly become the recommended way to implement authorization.

As we were evaluating possible solutions to improve Realtime security, we looked to our Auth implementation as inspiration for a cohesive security system.

Today, we're updating Realtime to respect PostgreSQL RLS policies, so you can define your security rules once and have them automatically apply everywhere!

Before diving deeper into our Realtime RLS implementation, let's briefly cover how RLS works in PostgreSQL.

## Row Level Security Primer

When you need to control access to individual rows of data, PostgreSQL has you covered with [Row Level Security (RLS) policies](https://www.postgresql.org/docs/current/ddl-rowsecurity.html).
An RLS policy is a snippet of SQL filtering which users have the authority to create/read/update/delete rows in a table.

For example, the following policy would allow users to select their own rows in a todos table:

```sql hideCopy
create policy todo_select_policy
    on todos for select
    using ( (select auth.uid()) = user_id );
```

which is equivalent to adding

{/* prettier-ignore */}
```sql hideCopy
select *
from todos
where auth.uid() = todos.user_id; -- Policy is implicitly added.
```

to queries.

Check out the [Row Level Security guide](https://supabase.com/docs/guides/auth/row-level-security) for more info on how to use RLS with your project.

## Realtime Design

Our Realtime server receives and decodes binary changes from PostgreSQL logical replication, converts those changes to JSON, and broadcasts them to all connected clients.

### Challenge for RLS

The challenge, when applying row level security to the replication stream is that the visibility of a row may be different for each user subscribed to a database table.

We recognized that to fully secure Realtime in accordance with row level security, a row's visibility must be checked separately for each user on every change.
However, this quickly becomes a performance bottleneck when the number of changes, or number of subscribers, is large.

Since we can't control the number of subscribers or the number of changed records, we instead focused on making the security check for each user on every change as fast as possible.

### Implementation Overview

With these challenges in mind, we upstreamed the security responsibility to the database. Write Ahead Log Realtime Unified Security (WALRUS) exposes a PostgreSQL
function that Realtime server invokes with database changes.

### WALRUS Implementation

[WALRUS](https://github.com/supabase/walrus) inspects each record in the replication change to:

- Identify the source table (e.g. `public.notes`).
- Identify the change's action (INSERT/UPDATE/DELETE/TRUNCATE\*).
- Query the `subscription` table to determine the connected users who are actively subscribed to the source table. The `subscription` table is kept up to date by the Realtime server and tracks all connected users and the tables they are currently subscribed to.
- For each subscriber:
  - Assume the identity of the subscriber.
  - Query the source table to see if the record is visible to that subscriber.
- Report the list of subscribers who are authorized to view the record back to Realtime server.

<small>*Realtime server does not broadcast TRUNCATE changes</small>

**Efficiently Query to Check Access**

To maximize throughput, the query used to evaluate if a row is visible to a subscriber always queries using the tables primary key.

For example:

```sql hideCopy
select exists (select 1 from some_table where id = 806);
```

When more than one subscriber exists, the query is wrapped in a [prepared statement](https://www.postgresql.org/docs/13/sql-prepare.html) to remove the cost of the PostgreSQL
query planner on subsequent calls. The query planner time is frequently 2-3x execution time for simple queries, so this immediately multiplies throughput in the most common cases!

```sql hideCopy
"Planning Time: 0.099 ms"
"Execution Time: 0.051 ms"
```

**Colocation**

Colocating the security engine with subscriber data inside PostgreSQL allows us to avoid significant overhead when applying RLS policies.
Namely, network round-trip latency and I/O bottlenecks are removed while connection overhead is reduced relative to testing each record's visibility by polling the database from a separate process.
Instead, the SQL function only consumes a single connection and performs no network I/O.

### Performance

The throughput performance of the database server is best measured in terms of record processing time. As the number of subscribers to a table grows, the time required to process each record,
and the resultant processing time also grows.

![supabase-realtime-processing-per-subscription](/images/blog/launch-week-three/realtime-row-level-security-in-postgresql/supabase-realtime-processing-per-subscription.png)

<div class="overflow-x-scroll" markdown="block">

| Subscribers          | 1    | 5    | 10   | 25   | 50   | 100  | 250  | 500  | 1,000 | 2,000 | 5,000 | 10,000 |
| -------------------- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ----- | ----- | ----- | ------ |
| Processing Time (ms) | 11.2 | 12.5 | 14.2 | 16.7 | 18.8 | 24.5 | 27.8 | 29.1 | 64.7  | 75.5  | 158.4 | 303.8  |

</div>

## Best Practices for Performance

To get the most out of Realtime row level security, follow these guidelines:

### Disable for public tables

If your data is insensitive or publicly available, such as stock prices listed under NASDAQ, then don't enable row level security!

The fastest security policy is one that doesn't exist :)

### Optimize your policies

If you do need row level security, make sure that your policies are fast.

Remember that your policy is executed _each_ time a query touches the table that the policy is applied to. If your policy is slow, all access to that table will be slow.
Avoid joins within RLS policies when you can, and make sure all filter conditions use an index.

Additionally, keep in mind that if you use joins within an RLS policy, any RLS policies on the tables you're joining to will also be executed in turn, adding to the overall overhead.

### Small primary keys

Keep your primary keys small and efficient.

Use single column primary keys with a fixed field size (integer, uuid, etc.) over text or multi-column indexes.

## Next Steps

Realtime RLS is available today on all existing and new Supabase projects. To get started, upgrade your Supabase JavaScript client to version v1.23.0
and launch your new PostgreSQL database today: [database.new](https://database.new)

## More Postgres resources

- [Implementing "seen by" functionality with Postgres](https://supabase.com/blog/seen-by-in-postgresql)
- [Partial data dumps using Postgres Row Level Security](https://supabase.com/blog/partial-postgresql-data-dumps-with-rls)
- [Postgres Views](https://supabase.com/blog/postgresql-views)
- [Postgres Auditing in 150 lines of SQL](https://supabase.com/blog/audit)
- [Cracking PostgreSQL Interview Questions](https://supabase.com/blog/cracking-postgres-interview)
- [What are PostgreSQL Templates?](https://supabase.com/blog/postgresql-templates)

## Credits

Authored by:

- [Oliver Rice](https://github.com/olirice)
- [Wen Bo Xie](https://github.com/w3b6x9)


## Links discovered
- [September 2019](https://github.com/supabase/realtime/commit/175f649784147af80acfc9ff5be9d160285c76ea)
- [Auth launch](https://supabase.io/blog/supabase-auth)
- [Row Level Security (RLS) policies](https://www.postgresql.org/docs/current/ddl-rowsecurity.html)
- [Row Level Security guide](https://supabase.com/docs/guides/auth/row-level-security)
- [WALRUS](https://github.com/supabase/walrus)
- [prepared statement](https://www.postgresql.org/docs/13/sql-prepare.html)
- [supabase-realtime-processing-per-subscription](https://github.com/supabase/supabase/blob/master/images/blog/launch-week-three/realtime-row-level-security-in-postgresql/supabase-realtime-processing-per-subscription.png)
- [database.new](https://database.new)
- [Implementing "seen by" functionality with Postgres](https://supabase.com/blog/seen-by-in-postgresql)
- [Partial data dumps using Postgres Row Level Security](https://supabase.com/blog/partial-postgresql-data-dumps-with-rls)
- [Postgres Views](https://supabase.com/blog/postgresql-views)
- [Postgres Auditing in 150 lines of SQL](https://supabase.com/blog/audit)
- [Cracking PostgreSQL Interview Questions](https://supabase.com/blog/cracking-postgres-interview)
- [What are PostgreSQL Templates?](https://supabase.com/blog/postgresql-templates)
- [Oliver Rice](https://github.com/olirice)
- [Wen Bo Xie](https://github.com/w3b6x9)

--- apps/www/_blog/2022-10-20-supabase-js-v2-released.mdx ---
---
title: 'supabase-js v2 Released'
description: We've released supabase-js v2. Updated examples, quickstarts, and an improved experience.
author: thor_schaeff
image: supabase-js-v2-release/supabase-js.jpg
thumb: supabase-js-v2-release/supabase-js.jpg
categories:
  - product
tags:
  - launch-week
date: '2022-10-20'
toc_depth: 3
---

During our [last Launch Week](/launch-week) we presented the [release candidate for supabase-js v2](/blog/supabase-js-v2). Since then we've been busy incorporating your feedback, [updating the docs](/docs/reference/javascript/), [examples](https://github.com/supabase/supabase/tree/master/examples), [quickstart guides](/docs/guides/with-nextjs), and putting a [migration guide](/docs/reference/javascript/v1/upgrade-guide) together.

## What is new in v2?

Enhanced TypeScript support built right in was the big one! Now you can use the CLI to generate types, directly from your database.

Plus v2 comes with lots of improvements that solved some of the largest pain-points highlighted by our users. Your feedback help us improve: you speak, we listen.

Read all the updates and the differences with v1 on the [announcement blog post](/blog/supabase-js-v2)

## Acknowledgements

It truly takes a village, well, in this case an entire community, and we want to thank you all for your feedback, and contributions!

Version 2.0 is the result of the combined work of several Supatroopers (Alaister, Andrew, Inian, Joel, Jon, Kang, Bobbie, and Tyler), over 100 contributors to our libraries, and over 450 contributors to our docs and websites.

If you're one of those contributors, thank you!

- [`functions-js`](https://github.com/supabase/functions-js/graphs/contributors) (4)
- [`gotrue-js`](https://github.com/supabase/gotrue-js/graphs/contributors) (47)
- [`postgrest-js`](https://github.com/supabase/postgrest-js/graphs/contributors) (30)
- [`realtime-js`](https://github.com/supabase/realtime-js/graphs/contributors) (16)
- [`storage-js`](https://github.com/supabase/storage-js/graphs/contributors) (17)
- [`supabase-js`](https://github.com/supabase/supabase-js/graphs/contributors) (39)

Special shout outs to: [@vejja](https://github.com/vejja), [@pixtron](https://github.com/pixtron), [@bnjmnt4n](https://github.com/bnjmnt4n), and [@karlseguin](https://github.com/karlseguin).

## Links

- [Documentation](/docs/reference/javascript)
- [Migration Guide](/docs/reference/javascript/v1/upgrade-guide)
- [Quickstart Guides](/docs/guides/with-nextjs)
- [Examples (GitHub)](https://github.com/supabase/supabase/tree/master/examples)
- [Release Notes](/docs/reference/javascript/release-notes)
- [Release Candidate Blogpost](/blog/supabase-js-v2)
- [Auth Helpers](/docs/guides/auth/auth-helpers/)
- [Auth UI](https://supabase.com/docs/guides/auth/auth-helpers/auth-ui)


## Links discovered
- [last Launch Week](https://github.com/supabase/supabase/blob/master/launch-week.md)
- [release candidate for supabase-js v2](https://github.com/supabase/supabase/blob/master/blog/supabase-js-v2.md)
- [updating the docs](https://github.com/supabase/supabase/blob/master/docs/reference/javascript.md)
- [examples](https://github.com/supabase/supabase/tree/master/examples)
- [quickstart guides](https://github.com/supabase/supabase/blob/master/docs/guides/with-nextjs.md)
- [migration guide](https://github.com/supabase/supabase/blob/master/docs/reference/javascript/v1/upgrade-guide.md)
- [announcement blog post](https://github.com/supabase/supabase/blob/master/blog/supabase-js-v2.md)
- [`functions-js`](https://github.com/supabase/functions-js/graphs/contributors)
- [`gotrue-js`](https://github.com/supabase/gotrue-js/graphs/contributors)
- [`postgrest-js`](https://github.com/supabase/postgrest-js/graphs/contributors)
- [`realtime-js`](https://github.com/supabase/realtime-js/graphs/contributors)
- [`storage-js`](https://github.com/supabase/storage-js/graphs/contributors)
- [`supabase-js`](https://github.com/supabase/supabase-js/graphs/contributors)
- [@vejja](https://github.com/vejja)
- [@pixtron](https://github.com/pixtron)
- [@bnjmnt4n](https://github.com/bnjmnt4n)
- [@karlseguin](https://github.com/karlseguin)
- [Documentation](https://github.com/supabase/supabase/blob/master/docs/reference/javascript.md)
- [Migration Guide](https://github.com/supabase/supabase/blob/master/docs/reference/javascript/v1/upgrade-guide.md)
- [Quickstart Guides](https://github.com/supabase/supabase/blob/master/docs/guides/with-nextjs.md)
- [Examples (GitHub)](https://github.com/supabase/supabase/tree/master/examples)
- [Release Notes](https://github.com/supabase/supabase/blob/master/docs/reference/javascript/release-notes.md)
- [Release Candidate Blogpost](https://github.com/supabase/supabase/blob/master/blog/supabase-js-v2.md)
- [Auth Helpers](https://github.com/supabase/supabase/blob/master/docs/guides/auth/auth-helpers.md)
- [Auth UI](https://supabase.com/docs/guides/auth/auth-helpers/auth-ui)

--- apps/www/_blog/2022-12-16-postgrest-11-prerelease.mdx ---
---
title: PostgREST 11 pre-release
description: Describes new features of PostgREST 11 pre-release
author: steve_chavez
image: lw6-community/postgrest.png
thumb: lw6-community/postgrest.png
categories:
  - postgres
tags:
  - postgres
  - launch-week
  - planetpg
date: '2022-12-16'
toc_depth: 3
---

PostgREST 11 is not wrapped up yet, however a pre-release with the **[latest features and fixes](https://github.com/PostgREST/postgrest/releases/tag/v10.1.1.20221212)**
is available on the Supabase CLI.

In this blog post we'll cover some of the improved querying capabilities: spreading related tables, related orders and anti-joins.

## Spreading related tables

Very often the way we structure a database is not the way we want to present it to the frontend application. For example, let's assume we have a `films` and `technical_specs` tables and
they form a one-to-one relationship.

Using PostgREST resource embedding, we can query them in one request like so

<small>From HTTP:</small>

```http
GET /films?select=title,technical_specs(camera,laboratory,sound_mix)
```

<small>or JavaScript:</small>

```jsx
const { data, error } = await supabase.from('films').select(`
    title,
    technical_specs (
      camera, laboratory, duration
    )
  `)
```

<small>Response:</small>

```json
[
  {
    "title": "Pulp Fiction",
    "technical_specs": {
      "camera": "Arriflex 35-III",
      "laboratory": "DeLuxe, Hollywood (CA), USA (color)",
      "duration": "02:34:00"
    }
  },
  "..."
]
```

But we'd like to present a “flattened” result to the frontend, without the `technical_specs` object. For this we could create a new database view or function that shapes the json the way we want, but creating extra database objects is not always convenient.

Using the new “spread” operator(syntax borrowed from [JS](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax)), we can expand a related table columns and remove the nested object.

<small>From HTTP:</small>

```jsx
GET /films?select=title,...technical_specs(camera,laboratory,duration)
```

<small>or JavaScript:</small>

```jsx
const { data, error } = await supabase.from('films').select(`
    title,
    ...technical_specs (
      camera, laboratory, duration
    )
  `)
```

<small>Response:</small>

```json
[
  {
    "title": "Pulp Fiction",
    "camera": "Arriflex 35-III",
    "laboratory": "DeLuxe, Hollywood (CA), USA (color)",
    "duration": "02:34:00"
  },
  "..."
]
```

This only works for one-to-one and many-to-one relationships for now but we're looking at ways to remove this restriction.

## Order by related tables

It's also a common use case to order a table by a related table column. For example, suppose you'd like to order `films` based on the `technical_specs.duration` column.

You can now do it like so:

<small>From HTTP:</small>

```http
GET /films?select=title,...technical_specs(duration)&order=technical_specs(duration).desc
```

<small>or JavaScript:</small>

```jsx
const { data, error } = await supabase
  .from('films')
  .select(`
    title,
    ...technical_specs (
      duration
    )
  `)
   .order('technical_specs(duration)', { descending: true }))
```

<small>Response:</small>

```json
[
  {
    "title": "Amra Ekta Cinema Banabo",
    "duration": "21:05:00"
  },
  {
    "title": "Resan",
    "duration": "14:33:00"
  },
  "..."
]
```

Similarly to spreading related tables, this only works for one-to-one and many-to-one relationships.

## Anti-Joins

To do the equivalent of a left anti-join, you can now filter the rows where the related table is `null`.

<small>From HTTP:</small>

```http
GET /films?select=title,nominations()&nominations=is.null
```

<small>or JavaScript:</small>

```jsx
const { data, error } = await supabase
  .from('films')
  .select(`
    title,
    nominations()
  `)
   .is('nominations', null))
```

<small>Response:</small>

```json
[
  {
    "title": "Memories of Murder"
  },
  {
    "title": "Rush"
  },
  {
    "title": "Groundhog Day"
  },
  "..."
]
```

Note that `nominations` doesn't select any columns so they don't show on the resulting response.

The equivalent of an inner join can be done by filtering the rows where the related table is `not null`.

```http
GET /films?select=title,nominations(rank,...competitions(name))&nominations=not.is.null
```

```jsx
const { data, error } = await supabase
  .from('films')
  .select(
    `
    title,
    nominations(rank,...competitions(name))
  `
  )
  .not('nominations', 'is', null)
```

<small>Response:</small>

```json
[
  {
    "title": "Pulp Fiction"
    "nominations": [
      {"rank": 1, "name": "Palme d'Or"},
      {"rank": 1, "name": "BAFTA Film Award"},
      {"..."}
    ]
  },
  "..."
]
```

This was already possible with the `!inner` modifier([introduced on PostgREST 9](https://supabase.com/blog/postgrest-9#resource-embedding-with-inner-joins))
but the `not null` filter is more flexible and can be used with an [or filter](https://supabase.com/docs/reference/javascript/or) to combine related tables' conditions.

## Try it out

This pre-release is not deployed to Supabase cloud but you can try it out locally with the [Supabase CLI](https://supabase.com/docs/reference/cli/introduction).

```bash
$ supabase start
```

Please try it and report any bugs, suggestions or ideas!

## More Launch Week 6

- [Day 1: New Supabase Docs, built with Next.js](https://supabase.com/blog/new-supabase-docs-built-with-nextjs)
- [Day 2: Supabase Storage v2: Image resizing and Smart CDN](https://supabase.com/blog/storage-image-resizing-smart-cdn)
- [Day 3: Multi-factor Authentication via Row Level Security Enforcement](https://supabase.com/blog/mfa-auth-via-rls)
- [Day 4: Supabase Wrappers, a Postgres FDW framework written in Rust](https://supabase.com/blog/postgres-foreign-data-wrappers-rust)
- [Day 5: Supabase Vault is now in Beta](https://supabase.com/blog/vault-now-in-beta)
- [Community Day](https://supabase.com/blog/launch-week-6-community-day)
- [Point in Time Recovery is now available](https://supabase.com/blog/postgres-point-in-time-recovery)
- [Custom Domain Names are now available](https://supabase.com/blog/custom-domain-names)
- [Wrap Up: everything we shipped](https://supabase.com/blog/launch-week-6-wrap-up)


## Links discovered
- [latest features and fixes](https://github.com/PostgREST/postgrest/releases/tag/v10.1.1.20221212)
- [JS](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax)
- [introduced on PostgREST 9](https://supabase.com/blog/postgrest-9#resource-embedding-with-inner-joins)
- [or filter](https://supabase.com/docs/reference/javascript/or)
- [Supabase CLI](https://supabase.com/docs/reference/cli/introduction)
- [Day 1: New Supabase Docs, built with Next.js](https://supabase.com/blog/new-supabase-docs-built-with-nextjs)
- [Day 2: Supabase Storage v2: Image resizing and Smart CDN](https://supabase.com/blog/storage-image-resizing-smart-cdn)
- [Day 3: Multi-factor Authentication via Row Level Security Enforcement](https://supabase.com/blog/mfa-auth-via-rls)
- [Day 4: Supabase Wrappers, a Postgres FDW framework written in Rust](https://supabase.com/blog/postgres-foreign-data-wrappers-rust)
- [Day 5: Supabase Vault is now in Beta](https://supabase.com/blog/vault-now-in-beta)
- [Community Day](https://supabase.com/blog/launch-week-6-community-day)
- [Point in Time Recovery is now available](https://supabase.com/blog/postgres-point-in-time-recovery)
- [Custom Domain Names are now available](https://supabase.com/blog/custom-domain-names)
- [Wrap Up: everything we shipped](https://supabase.com/blog/launch-week-6-wrap-up)

--- apps/www/_blog/2023-07-12-postgrest-11-1-release.mdx ---
---
title: 'What is new in PostgREST v11.1?'
description: 'Impersonated Role Settings, Configurable Isolation Level, improved Bulk Insert, and more'
author: steve_chavez
image: lw6-community/postgrest.png
thumb: lw6-community/postgrest.png
categories:
  - postgres
tags:
  - postgres
date: '2023-07-12'
toc_depth: 3
---

PostgREST 11.1 is now available on the Supabase platform. Besides the [pre-release](https://supabase.com/blog/postgrest-11-prerelease) features, we’ve added configuration and querying improvements. Here is what's new:

## Impersonated Role Settings

Every role that passes [PostgREST JWT Authentication](https://postgrest.org/en/stable/references/auth.html#jwt-based-user-impersonation) is an _impersonated role_. On the Supabase platform, these are the `anon`, `authenticated` and `service_role` roles.

These roles can now have settings applied with a regular `ALTER ROLE .. SET`. This is useful, for example, to prevent web users from running expensive queries.

Let’s try it by setting a statement timeout and cost limit.

### Statement timeout

`statement_timeout` aborts any statement that takes more than the specified amount of time. Let’s set it for the `anon`, `authenticated` and `service_role` roles:

```sql
-- anonymous users can run queries that take 100 milliseconds max
alter
  role anon
set
  statement_timeout = '100ms';

-- authenticated users can run queries that take 5 seconds max
alter
  role authenticated
set
  statement_timeout = '5s';

-- backend-only users can run queries that take 15 seconds max
alter
  role service_role
set
  statement_timeout = '15s';
```

You need to reload PostgREST config cache to apply these changes.

```sql
NOTIFY pgrst,
'reload config';
```

Now, suppose you do an expensive query with the `anon` role. Like filtering on a big table's unindexed column (this will cause a full table scan):

```jsx
const { data, error } = await supabase.from('big_table').select().eq('unindexed_column', 'value')
```

Then, after 5 seconds, the request will be aborted with the response:

```json
{
  "hint": null,
  "details": null,
  "code": "57014",
  "message": "canceling statement due to statement timeout"
}
```

Which is what we wanted. Note that there's already a global `statement_timeout` set but you can be more fine-grained with this feature. See [timeouts](https://supabase.com/docs/guides/database/timeouts) for more details.

### Statement Cost Limit

With a statement timeout, expensive queries will still get executed for a length of time. They'll consume resources until they’re terminated.

The [pg_plan_filter](https://github.com/pgexperts/pg_plan_filter) extension (available on the Supabase platform), brings a statement cost limit. This abort queries at the planning phase, before they get executed.

You can use it like:

```sql
-- anonymous users can only run cheap queries
ALTER
  USER anon
SET
  plan_filter.statement_cost_limit = 10000;

-- authenticated users can run more expensive queries
ALTER
  USER authenticated
SET
  plan_filter.statement_cost_limit = 1e6;

-- backend-only users can run any query
ALTER
  USER service_role
SET
  plan_filter.statement_cost_limit = 0;

NOTIFY pgrst,
'reload config';

-- reload postgREST config cache to apply changes
```

Let’s repeat the previous expensive query with the `anon` role.

```jsx
const { data, error } = await supabase.from('big_table').select().eq('unindexed_column', 'value')
```

Then, immediately, the request will be aborted and the response will be:

```jsx
{
  "hint": null,
  "details": null,
  "code": "54001",
  "message": "plan cost limit exceeded"
}
```

Note that tuning is required to get the cost limit right. You should use the `plan_filter.statement_cost_limit` with care as it can invalidate legitimate queries.

## Configurable Transaction Isolation Level

By default, all queries run in a transaction with the default _read committed_ isolation level.

You can now modify this with the `default_transaction_isolation` setting.

If you want a function to run with _repeatable read_ isolation level:

```sql
create function hello()
returns text as $$
  select 'hello';
$$ language sql
set default_transaction_isolation = 'repeatable read';
```

Or if you want an impersonated role to run its queries with a _serializable_ isolation level:

```sql
alter
  role service_role
set
  default_transaction_isolation = 'serializable';

NOTIFY pgrst,
'reload config';

-- reload postgREST config cache
```

Note that the default _read committed_ is good enough for almost all use cases. Higher isolation levels incur in overhead as they use more sophisticated locking. They're only needed in special cases.

## Bulk insert JSON with default values

A long wanted feature was bulk inserting JSON while considering columns' default values.

Having the following sample table.

```sql
create table
  foo (
    id bigint generated by default as identity primary key,
    bar text,
    baz int default 100
  );
```

You can now do it like this:

```jsx
const { error } = await supabase
  .from('foo')
  .insert([
	  { "bar": "val1"
	  }
	, { "bar": "val2"
	  , "baz": 15
	  }
	], defaultToNull: false)
  .select()
```

And the response will be:

```json
[
  { "id": 1, "bar": "val1", "baz": 100 },
  { "id": 2, "bar": "val2", "baz": 15 }
]
```

As you can see, `id` and `baz` took their default values.

## ANY/ALL filter modifiers

As a shortcut to `OR` filters, you can now use `any` modifiers on various filters. Take the `like` filter as an example:

{/* prettier-ignore */}
```jsx
const res = await postgrest
  .from('users')
  .select()
  .likeAnyOf('username', ['%supa%', '%kiwi%'])
```

This is equivalent to the following in SQL.

```sql
select *
from users
where username like ANY('{%supa%,%kiwi%}');
```

`any` modifiers are available for the `eq,like,ilike,gt,gte,lt,lte,match,imatch` filters.

For completeness, the `all` modifier is also included.

## Minimal Breaking Changes from v10

If you only use PostgREST through Supabase client libraries (like [`supabase-js`](https://supabase.com/docs/reference/javascript/introduction)) then it's safe to upgrade to v11. If you use PostgREST with other HTTP clients (like `curl`), consider the breaking changes for this version:

- The `Range` header is now only considered on GET requests and is ignored for any other method. Previously PostgREST responded with an error but [RFC 9110](https://www.rfc-editor.org/rfc/rfc9110.html#name-range) dictates that we should ignore the `Range` header instead.
- RPC requests no longer consider the `Prefer: params=multiple-objects` header. This header was already deprecated on [v10.1.0](https://github.com/PostgREST/postgrest/releases/tag/v10.1.0).

By making use of [Logflare](https://logflare.app/), we detected that out of 20 thousands of projects:

- Only 7 projects used `Range` for HTTP methods other than GET. In these cases all responses were errors so in fact this breaking change is a fix for those requests.
- None were using `Prefer: params=multiple-objects`.

So overall the breaking changes are minimal.

## Closing up

There you have it, now you can make your API more secure with role settings and use higher isolation levels without resorting to direct PostgreSQL connections.

PostgREST v11.1 is available for all Supabase projects created after 5 July 2023. Existing projects can upgrade by doing a pause/unpause.

## More Postgres resources

- [Storing OpenAI embeddings in Postgres with pgvector](https://supabase.com/blog/openai-embeddings-postgres-vector)
- [Choosing a Postgres Primary Key](https://supabase.com/blog/choosing-a-postgres-primary-key)
- [SQL or NoSQL? Why not use both (with PostgreSQL)?](https://supabase.com/blog/sql-or-nosql-both-with-postgresql)
- [pg_jsonschema: JSON Schema support for Postgres](https://supabase.com/blog/pg-jsonschema-a-postgres-extension-for-json-validation)
- [Implementing "seen by" functionality with Postgres](https://supabase.com/blog/seen-by-in-postgresql)


## Links discovered
- [pre-release](https://supabase.com/blog/postgrest-11-prerelease)
- [PostgREST JWT Authentication](https://postgrest.org/en/stable/references/auth.html#jwt-based-user-impersonation)
- [timeouts](https://supabase.com/docs/guides/database/timeouts)
- [pg_plan_filter](https://github.com/pgexperts/pg_plan_filter)
- [`supabase-js`](https://supabase.com/docs/reference/javascript/introduction)
- [RFC 9110](https://www.rfc-editor.org/rfc/rfc9110.html#name-range)
- [v10.1.0](https://github.com/PostgREST/postgrest/releases/tag/v10.1.0)
- [Logflare](https://logflare.app/)
- [Storing OpenAI embeddings in Postgres with pgvector](https://supabase.com/blog/openai-embeddings-postgres-vector)
- [Choosing a Postgres Primary Key](https://supabase.com/blog/choosing-a-postgres-primary-key)
- [SQL or NoSQL? Why not use both (with PostgreSQL)?](https://supabase.com/blog/sql-or-nosql-both-with-postgresql)
- [pg_jsonschema: JSON Schema support for Postgres](https://supabase.com/blog/pg-jsonschema-a-postgres-extension-for-json-validation)
- [Implementing "seen by" functionality with Postgres](https://supabase.com/blog/seen-by-in-postgresql)

--- apps/www/_blog/2024-04-19-security-performance-advisor.mdx ---
---
title: 'Supabase Security Advisor & Performance Advisor'
description: "We're making it easier to build a secure and high-performing application."
author: saltcod,oli_rice
image: ga-week/security-peformance-advisor/og.png?v=3
thumb: ga-week/security-peformance-advisor/thumb.png?v=3
categories:
  - product
tags:
  - launch-week
date: '2024-04-18'
toc_depth: 3
launchweek: '11'
---

We're dropping some handy tools in Supabase Studio this week to help with security and performance:

1. **Security Advisor:** for detecting insecure database configuration
2. **Performance Advisor**: for suggesting database optimizations
3. **Index Advisor**: for suggesting indexes on slow-running queries

We [announced General Availability](/ga) this week, reaching a point where we feel confident our organization can support all types of customers and help them become successful, regardless of their demands. It's a big milestone after four years of building.

As we've grown up as a company, so too have our customers. Many of you have been with us since the start and have seen your projects grow from 0 to literally millions of users, scaling from the Free Plan up to the largest size servers we offer.

<div className="video-container">
  <iframe
    className="w-full"
    src="https://www.youtube-nocookie.com/embed/NZEbVe47DfA"
    title="Supabase Security Advisor & Performance Advisor"
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; fullscreen; gyroscope; picture-in-picture; web-share"
    allowfullscreen
  />
</div>

## Helping you help yourself

Along with this growth, we've learned many lessons about the types of issues developers encounter using Postgres, especially as they start to get traction. We've built tooling, documentation, and support processes around common issues related to security, performance, resource usage, and slow queries.

As we've helped hundreds of thousands of customers through issues like these, a trend emerged: developers want their problems resolved quickly, but they also want to know what happened and why. This is the typical profile of a Supabase developer - thoughtful, curious, and hungry to learn more about the inner workings of Postgres.

This week, we're adding features into Supabase Studio to address common issues as you scale up. These are powered by tools that we have open sourced this week: [index_advisor](https://github.com/supabase/index_advisor) and [splinter](https://github.com/supabase/splinter) (“**S**upabase **P**ostgres **linter**").

## Security Advisor

<Img
  alt="security-peformance-advisor"
  src={{
    light: '/images/blog/ga-week/security-peformance-advisor/security-advisor--light.png',
    dark: '/images/blog/ga-week/security-peformance-advisor/security-advisor.png',
  }}
  captionAlign="left"
  zoomable={false}
/>

This week we're adding a Security Advisor to Supabase Studio. This is a new interface for exploring security issues with your database because, well, sometimes even Postgres veterans get it wrong. The Security Advisor runs a set queries on your database to identify configuration issues.

The Security Advisor is helpful in pointing out security issues that you might have forgotten or not yet be aware: some lints are general purpose for Postgres projects, while others are specific to Supabase.

As with all of our tooling, it's designed to both help and to teach. The suggestions are well-documented with a rationale, descriptions, examples and remediation steps. Did you know, for example, that views don't respect RLS policies unless you've set `security_invoker=on`? Now you do!

## Performance Advisor

<Img
  alt="security-peformance-advisor"
  src={{
    light: '/images/blog/ga-week/security-peformance-advisor/performance-advisor--light.png',
    dark: '/images/blog/ga-week/security-peformance-advisor/performance-advisor.png',
  }}
  captionAlign="left"
  zoomable={false}
/>

While database tuning is a speciality on its own, many projects have simple optimizations to improve performance. We're releasing a new Performance Advisor in Supabase Studio to surface the low-hanging fruit.

The Performance Advisor checks for misconfigurations, like tables with unindexed foreign key columns, inefficient RLS policies, or columns with duplicate indexes. As a project grows, issues like this can sneak in and slow your projects down (and fill up your disks).

If you're looking for ways to speed up your database, this is the place to start.

## Bonus: Index Advisor

<Img
  alt="security-peformance-advisor"
  src={{
    light: '/images/blog/ga-week/security-peformance-advisor/index-advisor--light.png',
    dark: '/images/blog/ga-week/security-peformance-advisor/index-advisor.png',
  }}
  captionAlign="left"
  zoomable={false}
/>

Speaking of performance, we have another treat for you. Last week, we announced [PostgreSQL index advisor](https://news.ycombinator.com/item?id=40028111) on Hacker News. This is a Postgres extension that can determine if a given query should have an index. It's already proving useful:

<Quote img="yc.png" caption="noob-4-life on Hacker News">
  Awesome, the Index Advisor sped my slowest query 4x!
</Quote>

The Supabase [Index Advisor](https://github.com/supabase/index_advisor) is now available inside Supabase Studio. We've integrated the Index Advisor into our existing Query Performance tool so that you can find your slowest queries and check recommendations. As its name suggests, this analyzes your queries and make recommendations to add or remove table indexes.

<details>
  <summary>What is an index?</summary>
  <div>
    Not sure what an index is? Imagine having to look up a person's name in a phonebook where the
    entries are not alphabetical. This is what your database tables look like by default. Finding a
    number from a randomly sorted list of records would take a long time. When you add an index, the
    database stores the sorted values, allowing it to quickly locate a row without having to search
    through every record sequentially.
  </div>
</details>

This is just the beginning of our plan to make automated data analysis tooling available to all developers. Even if you're experienced with databases, this will be a huge help with the optimization work you have already planned to do. If you're new to databases, the Index Advisor will help you level-up, surfacing issues and showing you how to fix them.

Let's have a look at some queries:

<Img
  alt="security-peformance-advisor"
  src={{
    light: '/images/blog/ga-week/security-peformance-advisor/no-index--light.png',
    dark: '/images/blog/ga-week/security-peformance-advisor/no-index.png',
  }}
  captionAlign="left"
  zoomable={false}
  caption="This query doesn't need an index"
/>

<Img
  alt="security-peformance-advisor"
  src={{
    light: '/images/blog/ga-week/security-peformance-advisor/needs-index--light.png',
    dark: '/images/blog/ga-week/security-peformance-advisor/needs-index.png',
  }}
  captionAlign="left"
  zoomable={false}
  caption="This query would benefit from an index"
/>

## What's next

We plan to expand the set of suggestions available in Studio to cover more areas of potential improvement for security and performance. Some of the ideas we have in mind for the future include:

- checking for liberally-permissioned columns that contain personally identifiable information (PII)
- identifying bloated tables/indexes
- advanced Postgres configuration
- suggestions for tighting up Supabase Auth as you move into production

### Contributions welcome

Community feedback plays a key role in helping us determine where to invest time developing future lints. We encourage contributions by suggesting new lints or enhancements.

If you have ideas for new lints or wish to report problems you can open an issue on our GitHub repository [`splinter`](https://github.com/supabase/splinter) or [`index_advisor`](https://github.com/supabase/index_advisor).


## Links discovered
- [announced General Availability](https://github.com/supabase/supabase/blob/master/ga.md)
- [index_advisor](https://github.com/supabase/index_advisor)
- [splinter](https://github.com/supabase/splinter)
- [PostgreSQL index advisor](https://news.ycombinator.com/item?id=40028111)
- [Index Advisor](https://github.com/supabase/index_advisor)
- [`splinter`](https://github.com/supabase/splinter)
- [`index_advisor`](https://github.com/supabase/index_advisor)

--- apps/www/_blog/2025-07-16-improved-security-controls.mdx ---
---
title: 'Improved Security Controls and A New Home for Security'
description: 'Access to a new central security page and launch of additional controls.'
categories:
  - product
  - launch-week
tags:
  - launch-week
  - security
  - realtime
date: '2025-07-16:15:00'
toc_depth: 3
author: staaldraad,hieu,filipe
image: launch-week-15/day-3-security-controls/og.jpg
thumb: launch-week-15/day-3-security-controls/thumb.png
launchweek: '15'
---

Today we are launching the foundations of several security features we plan to build on in the upcoming months.

- Centralized security docs
- Organization‑wide security settings in the Dashboard

## Centralized Security Docs

Supabase offers a robust set of security controls, but discovering and configuring them can feel daunting. Our [new security documentation](/docs/guides/security) brings everything into one place - from product features like Auth Rate Limits and Vault to step‑by‑step guides on building secure applications with Supabase (Row‑Level Security, hardening the Data API, the Production Checklist, and more).

We’ve also published dedicated [SOC 2](/docs/guides/security/soc-2-compliance) and [HIPAA](/docs/guides/security/hipaa-compliance) guides that explain how to achieve these compliance standards on Supabase and answer common questions.

## Enforce MFA in Organization Security Settings

<Img
  src={{
    dark: '/images/blog/launch-week-15/day-3-security-controls/mfa-enforced-dark.png',
    light: '/images/blog/launch-week-15/day-3-security-controls/mfa-enforced-light.png',
  }}
  alt="Organization view with a MFA enforced project"
/>

The first setting we are launching in the organization‑wide security settings page in the Dashboard is the ability to enforce Multi‑Factor Authentication (MFA) for every member of a Supabase Organization. Once enabled, all members must have MFA configured to access any project or resource in that org.

<Img
  src={{
    dark: '/images/blog/launch-week-15/day-3-security-controls/mfa-enforced-project-dark.png',
    light: '/images/blog/launch-week-15/day-3-security-controls/mfa-enforced-project-light.png',
  }}
  alt="Project view when MFA enforced and access denied"
/>

With MFA enforcement enabled, all members of your organization must use multi-factor authentication to access any project or resource. If a member hasn’t enabled MFA, they will immediately lose access until they do. New organization members will be able to accept invitations to an MFA enforced organization, but will not be able to interact with the organization until they have enabled MFA.

This setting is only available to **Organization Owners**, and the owner must have MFA enabled on their own account. We recommend setting up **two separate MFA apps** as a backup.

A few notes:

- Only available on **Pro, Team, and Enterprise** plans.
- **Personal Access Tokens** (**PATs**) are not affected by this setting.

You can toggle on this setting in the new [**Security tab**](/dashboard/org/_/security) of your organization settings.

<Img
  src={{
    dark: '/images/blog/launch-week-15/day-3-security-controls/security-page-dark.png',
    light: '/images/blog/launch-week-15/day-3-security-controls/security-page-light.png',
  }}
  alt="New security tab under organization settings"
/>

## Supabase Realtime - Enable Private Channels Only

<Img
  src={{
    dark: '/images/blog/launch-week-15/day-3-security-controls/realtime-settings-dark.png',
    light: '/images/blog/launch-week-15/day-3-security-controls/realtime-settings-light.png',
  }}
  alt="Realtime configuration for private channels"
/>

You can now set Realtime to use only private channels using [Realtime Authorization](/docs/guides/realtime/authorization?queryGroups=language&language=dart). If you toggle off the `Allow public access` setting, no public channels can be created. Only clients authorized via [Realtime Authorization](/docs/guides/realtime/authorization?queryGroups=language&language=dart), can listen to and send messages.

This settings page is under a feature preview and you can enable it [here](/dashboard/project/_?featurePreviewModal=supabase-ui-realtime-settings). Once the feature preview is enabled, you can configure this setting in the new [Realtime Settings page](/dashboard/project/_/realtime/settings). While you are there, you can also tune the connection pool size that Realtime uses and the maximum **concurrent clients.**

## Security and Performance Advisors - Disable Specific Rules

<Img
  src={{
    dark: '/images/blog/launch-week-15/day-3-security-controls/security-advisor-rules-dark.png',
    light: '/images/blog/launch-week-15/day-3-security-controls/security-advisor-rules-light.png',
  }}
  alt="Security and Performance Advisors - Rules Overview"
/>

We received feedback from users that not all security and performance advisor rules apply to their project. Supabase powers everything from backend‑only APIs to full‑stack apps and some Security and Performance advisors may not be applicable for everyone. For example, the RLS Disabled in Public rule may not apply if you only access Supabase from a secure context like a web server.

<Img
  src={{
    dark: '/images/blog/launch-week-15/day-3-security-controls/security-advisor-individual-rule-dark.png',
    light:
      '/images/blog/launch-week-15/day-3-security-controls/security-advisor-individual-rule-light.png',
  }}
  alt="Security and Performance Advisors - Disable Rule"
/>

You can now customize Security Advisor rules and disable rules which are not relevant to your project. We will be extending rule customization to include rule assignment and more fine grained filtering.

This is currently under a feature preview and you can enable it [here](/dashboard/project/_?featurePreviewModal=supabase-ui-advisor-rules). Once enabled, rules can be managed through the new [configuration section](/dashboard/project/_/advisors/rules/security).

## What comes next?

This release is the first building block in our security roadmap across the Supabase platform, including user auth, network isolation, compliance tooling, and automated remediation.

Here’s what's in progress:

**Stronger Authentication and Access Control**

- **YubiKey and hardware key MFA support** to complement Time-based One-Time Password (TOTP) flow.
- We have already announced that [project scoped roles](/docs/guides/platform/access-control#organization-scoped-roles-vs-project-scoped-roles) are [available on the Team plan](/changelog#project-scoped-roles), and now we are working to bring **custom roles** to our Enterprise plan. This will allow organizations to define custom, fine grained roles, limiting the actions and resources users have access to.

**Security Enforcement**

- **Assigning Security Advisories** to team members in your org.
- Furthermore, we are extending our project scoped controls to allow **automatically enforcing compliance controls** on [sensitive projects](/docs/guides/platform/hipaa-projects).
- Supporting **additional compliance standards**, alongside our existing [SOC 2](/docs/guides/security/soc-2-compliance) and [HIPAA](/docs/guides/security/hipaa-compliance) controls.

**Enterprise Connectivity**

- **Self-service SSO for Supabase Organizations:** Enterprise teams looking to enforce SSO sign-on will be able to self-serve via Supabase Dashboard and will no longer need to submit a support ticket.
- [Supabase PrivateLink](/docs/guides/platform/privatelink) provides enterprise-grade private network connectivity between your AWS VPC and your Supabase database using AWS VPC Lattice. This is currently in Private Alpha and available to our Enterprise customers.

Our goal is to provide you with the best suite of security tools you need to deploy your production apps on Supabase with confidence.


## Links discovered
- [new security documentation](https://github.com/supabase/supabase/blob/master/docs/guides/security.md)
- [SOC 2](https://github.com/supabase/supabase/blob/master/docs/guides/security/soc-2-compliance.md)
- [HIPAA](https://github.com/supabase/supabase/blob/master/docs/guides/security/hipaa-compliance.md)
- [**Security tab**](https://github.com/supabase/supabase/blob/master/dashboard/org/_/security.md)
- [Realtime Authorization](https://github.com/supabase/supabase/blob/master/docs/guides/realtime/authorization?queryGroups=language&language=dart.md)
- [here](https://github.com/supabase/supabase/blob/master/dashboard/project/_?featurePreviewModal=supabase-ui-realtime-settings.md)
- [Realtime Settings page](https://github.com/supabase/supabase/blob/master/dashboard/project/_/realtime/settings.md)
- [here](https://github.com/supabase/supabase/blob/master/dashboard/project/_?featurePreviewModal=supabase-ui-advisor-rules.md)
- [configuration section](https://github.com/supabase/supabase/blob/master/dashboard/project/_/advisors/rules/security.md)
- [project scoped roles](https://github.com/supabase/supabase/blob/master/docs/guides/platform/access-control#organization-scoped-roles-vs-project-scoped-roles.md)
- [available on the Team plan](https://github.com/supabase/supabase/blob/master/changelog#project-scoped-roles.md)
- [sensitive projects](https://github.com/supabase/supabase/blob/master/docs/guides/platform/hipaa-projects.md)
- [Supabase PrivateLink](https://github.com/supabase/supabase/blob/master/docs/guides/platform/privatelink.md)

--- apps/www/_blog/2025-09-30-postgrest-13-release.mdx ---
---
title: PostgREST 13
description: New features and changes in PostgREST version 13.
author: steve_chavez,laurenceisla,avallete
image: 2025-09-30-postgrest-13-release/postgrest-13-release.png
thumb: 2025-09-30-postgrest-13-release/postgrest-13-release.png
categories:
  - postgres
tags:
  - postgres
  - postgrest
  - release-notes
date: '2025-09-30'
---

PostgREST 13 is out! It comes with API and Observabilty improvements. In this post, we'll see what's new.

## Spread To-Many relationships

This new feature allows you to represent one-to-many and many-to-many relationships as flat JSON arrays.

For example, if you have database similar to IMDB and you’d like to represent it as a hierarchical JSON structure for your frontend, like so:

```json
[
  {
    "title": "The Shawshank Redemption",
    "actors": ["Tim Robbins", "Morgan Freeman"],
    "genres": ["Drama"]
  },
  {
    "title": "The Godfather",
    "actors": ["Marlon Brando", "Al Pacino"],
    "genres": ["Drama", "Crime"]
  },
  {
    "title": "The Dark Knight",
    "actors": ["Christian Bale", "Heath Ledger"],
    "genres": ["Drama", "Crime", "Action"]
  }
]
```

You can now do it this way:

<$CodeTabs>

```js name=JavaScript
const { data, error } = await supabase.from('titles').select(`
  title:primary_title,
  ...people(actors:primary_name),
  ...genres(genres:name)
`)
```

```bash name=Bash
curl --get 'https://<your-domain>/titles'
  -d "select=title:primary_title,...people(actors:primary_name),...genres(genres:name)"
  -H "Authorization: Bearer <YOUR_KEY>"
```

```swift name=Swift
try await supabase.from("titles").select(
"""
  title:primary_title,
  ...people(actors:primary_name),
  ...genres(genres:name)
""")
```

</$CodeTabs>

The above `...people` is “spreading” the many-to-many relationship between `titles` and `people`, forming a flat array only consisting of the `primary_name` column. This flat array is then renamed to `actors`. We do a similar process for `genres` , which also forms a many-to-many relationship with `people`.

You can see the data model used for this example on this [gist](https://gist.github.com/steve-chavez/93f7ae04b4323e1952710af7129b32cf). There are more details about this feature on the [official docs](https://docs.postgrest.org/en/v13/references/api/resource_embedding.html#spread-to-many-relationships).

## Automatic tsvector convertion

Previously you could only use the full text search operator on `tsvector` columns, now you can do it on `text` and `json/jsonb` columns too:

<$CodeTabs>

```js name=JavaScript
const { data, error } = await supabase.from('titles').textSearch('primary_name', `'god' & 'father'`)
```

```bash name=Bash
curl --get 'https://<your-domain>/titles'
  -d "primary_name=fts.'god'%26'father"
  -H "Authorization: Bearer <YOUR_KEY>"
```

```swift name=Swift
try await supabase
  .from("titles")
  .fts("primary_name",value: "'god' & 'father'")
```

</$CodeTabs>

This works because `text` and `json/jsonb` columns will be automatically converted with `to_tsvector`.

To ensure this operation is fast, add an index:

```sql
create index idx_titles on people
using gin (to_tsvector('english', primary_name));
```

## Max Affected

You can now limit to the amount of rows affected by an `update` or `delete` operation with `maxAffected`:

<$CodeTabs>

```js name=JavaScript
const { data, error } = await supabase
  .from('people')
  .update({ primary_name: 'Marlon Brando Jr.' })
  .eq('nconst', 'nm0000008')
  .maxAffected(1)

// This is available starting from supabase-js version 2.56.0
```

```bash name=Bash
curl -X PATCH 'https://<your-domain>/people' \
  -d "nconst=eq.nm0000008" \
  -H "Authorization: Bearer <YOUR_KEY>" \
  -H "Prefer: handling=strict, max-affected=1" \
  -H "Content-Type: application/json" \
  -d @- <<JSON
{ primary_name: 'Marlon Brando Jr.' }
JSON
```

```swift name=Swift
try await supabase
  .from("people")
  .update(["primary_name": "Marlon Brando Jr."])
  .eq("nconst", value: "nm0000008")
  .maxAffected(1)
  .execute()

// This is available starting from supabase-swift version 2.33.0
```

</$CodeTabs>

If the rows affected by the operation surpass the limit in `maxAffected`, an error will be thrown.

This also works with `rpc()`, given that it modifies rows and returns the affected rows. More on details on the [official docs](https://docs.postgrest.org/en/v13/references/api/preferences.html#max-affected).

## Content-Length header

For observability, you can now verify the response body size in bytes in the `Content-Length` header.

```bash
HTTP/1.1 200 OK
Content-Length: 104
Content-Location: /items
```

This helps in cases where you want to know which requests consume the most traffic to avoid exceeding egress limits.

## Proxy-Status header

The PostgREST error code is now present in the `Proxy-Status` header.

```bash
HTTP/1.1 406 Not Acceptable
Proxy-Status: PostgREST; error=PGRST116
```

You can check the `Proxy-Status` and `Content-Length` headers in the Supabase Logs Explorer.

## Breaking Changes

### JWT `kid` validation

PostgREST now validates the JWT `kid` claim. If your JWT contains a Key ID (`kid`), it will try to match this with one of the `kid`'s in the configured JSON Web Key Set. Check the [official docs](https://docs.postgrest.org/en/v13/references/auth.html#jwk-kid-validation) for more details.

If you use Supabase Auth or the CLI to create JSON Web Keys, you shouldn’t worry about this change as both systems will ensure `kid`'s are present in the JSON Web Key Set.

For users that integrate with other Auth systems, make sure that both your JWT and JWKS follow the above rules.

### Schema validation in PostgREST search path

The schemas inside `db-schemas` and `db-extra-search-path` are now validated. This means you cannot put a nonexistent schema there, if you do PostgREST will fail with an error message.

If you drop a schema during a migration, you should make sure this is synced with the PostgREST search path, which is possible thanks to postgres transactional DDL:

```sql
begin;
drop schema old_schema;
alter role authenticator set pgrst.db_schemas = 'public, pg_graphql, others'; -- make sure old_schema is not present here
commit;
```

## Try it out

PostgREST v13 is now available for all new projects on the Supabase platform, old projects can upgrade to get this new version.

You can look at the full changelog on the [release notes](https://github.com/PostgREST/postgrest/releases/tag/v13.0.0).


## Links discovered
- [gist](https://gist.github.com/steve-chavez/93f7ae04b4323e1952710af7129b32cf)
- [official docs](https://docs.postgrest.org/en/v13/references/api/resource_embedding.html#spread-to-many-relationships)
- [official docs](https://docs.postgrest.org/en/v13/references/api/preferences.html#max-affected)
- [official docs](https://docs.postgrest.org/en/v13/references/auth.html#jwk-kid-validation)
- [release notes](https://github.com/PostgREST/postgrest/releases/tag/v13.0.0)

--- .github/pull_request_template.md ---
## I have read the [CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md) file.

YES/NO

## What kind of change does this PR introduce?

Bug fix, feature, docs update, ...

## What is the current behavior?

Please link any relevant issues here.

## What is the new behavior?

Feel free to include screenshots if it includes visual changes.

## Additional context

Add any other context or screenshots.


## Links discovered
- [CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md)

--- DEVELOPERS.md ---
# Developing Supabase

- [Developing Supabase](#developing-supabase)

  - [Getting started](#getting-started)
    - [Install dependencies](#install-dependencies)
  - [Local development](#local-development)
    - [Fork the repo](#fork-the-repo)
    - [Clone the repo](#clone-the-repo)
    - [Install dependencies](#install-dependencies-1)
      - [Running sites individually](#running-sites-individually)
      - [Shared components](#shared-components)
      - [Installing packages](#installing-packages)
  - [Running Docker for Supabase Studio](#running-docker-for-supabase-studio)
    - [Prerequisites](#prerequisites)
    - [Get Started](#get-started)
  - [Create a pull request](#create-a-pull-request)
  - [Issue assignment](#issue-assignment)
  - [Common tasks](#common-tasks)
    - [Add a redirect](#add-a-redirect)
    - [Federated docs](#federated-docs)
  - [Community channels](#community-channels)
  - [Contributors](#contributors)

- [Community channels](#community-channels)

## Getting started

Thank you for your interest in [Supabase](https://supabase.com) and your willingness to contribute!

To ensure a positive and inclusive environment, please read our [code of conduct](https://github.com/supabase/.github/blob/main/CODE_OF_CONDUCT.md). We encourage you to explore the existing [issues](https://github.com/supabase/supabase/issues) to see how you can make a meaningful impact. This document will help you setup your development environment.

### Install dependencies

You will need to install and configure the following dependencies on your machine to build [Supabase](https://supabase.com):

- [Git](https://git-scm.com/)
- [Node.js v22.x or higher](https://nodejs.org)
- [pnpm](https://pnpm.io/) version 9.x.x or higher
- [make](https://www.gnu.org/software/make/) or the equivalent to `build-essentials` for your OS
- [Docker](https://docs.docker.com/get-docker/) (to run studio locally)

## Local development

This repo uses [Turborepo](https://turborepo.org/docs).

All of our apps are in this [Turborepo](https://turborepo.org/docs), which make it easy to share packages and config between projects.

### Fork the repo

To contribute code to [Supabase](https://supabase.com), you must fork the [Supabase repo](https://github.com/supabase/supabase).

### Clone the repo

1. Clone your GitHub forked repo:

   ```sh
   git clone https://github.com/<github_username>/supabase.git
   ```

2. Go to the Supabase directory:
   ```sh
   cd supabase
   ```

### Install dependencies

1. Install the dependencies in the root of the repo:

   ```sh
   pnpm install # install dependencies
   ```

2. Copy the example `.env.local.example` to `.env.local`

   ```sh
   cp apps/www/.env.local.example apps/www/.env.local
   ```

3. After that you can run the apps simultaneously with the following:
   ```sh
   pnpm dev # start all the applications
   ```

Then visit, and edit, any of the following sites:

| Site                                                     | Directory      | Scope name | Description                                   | Local development server   |
| -------------------------------------------------------- | -------------- | ---------- | --------------------------------------------- | -------------------------- |
| [supabase.com](https://supabase.com)                     | `/apps/www`    | www        | The main website                              | http://localhost:3000      |
| [supabase.com/dashboard](https://supabase.com/dashboard) | `/apps/studio` | studio     | Studio dashboard (requires Docker, see below) | http://localhost:8082      |
| [supabase.com/docs](https://supabase.com/docs)           | `/apps/docs`   | docs       | Guides and Reference (Next.js based)          | http://localhost:3001/docs |

#### Running sites individually

You can run any of the sites individually by using the scope name. For example:

```sh
pnpm dev:www
```

Note: Particularly for `www` make sure you have copied `apps/www/.env.local.example` to `apps/www/.env.local`

#### Shared components

The monorepo has a set of shared components under `/packages`:

- `/packages/ai-commands`: Helpers/Commands for AI related functions
- `/packages/common`: Common React components, shared between all sites
- `/packages/config`: All shared config
- `/packages/shared-data`: Shared data that can be used across all apps
- `/packages/tsconfig`: Shared Typescript settings
- `/packages/ui`: Common UI components

#### Installing packages

Installing a package in a specific workspace requires you to move to the workspace and then run the install command.

For example:

1. `cd apps/studio`: move to the `studio` workspace.
2. `pnpm add react`: installs `react` into `studio` workspace.


---

## Running Docker for Supabase Studio

To run Studio locally, you'll need to setup Docker in addition to your NextJS frontend.

#### Prerequisites

First, make sure you have the Docker installed on your device. You can download and install it from [here](https://docs.docker.com/get-docker/).

#### Get Started

1. Navigate to the `docker` directory in your forked repo

   ```sh
   cd docker
   ```

2. Copy the example `env` file

   ```sh
   cp .env.example .env
   ```

3. Run docker:

   ```sh
   docker compose up
   ```

This command initializes the containers specified in the `docker-compose.yml` file. It might take a few moments to complete, depending on your computer and internet connection.

Once the `docker compose up` process completes, you should have your local version of Supabase up and running within Docker containers. You can access it at `http://localhost:8082`.

Remember to keep the Docker application open as long as you're working with your local Supabase instance.

## Create a pull request

After making any changes, open a pull request. Once you submit your pull request, the Supabase team will review it with you.

Once your PR has been merged, you will be proudly listed as a contributor in the [contributor chart](https://github.com/supabase/supabase/graphs/contributors)!

## Issue assignment

We don't have a process for assigning issues to contributors. Please feel free to jump into any issues in this repo that you are able to help with. Our intention is to encourage anyone to help without feeling burdened by an assigned task. Life can sometimes get in the way, and we don't want to leave contributors feeling obligated to complete issues when they may have limited time or unexpected commitments.

We also recognize that not having a process can sometimes lead to competing or duplicate PRs. There's no perfect solution here. We encourage you to communicate early and often on an Issue to indicate that you're actively working on it. If you see that an Issue already has a PR, try working with that author instead of drafting your own.

We review PRs in the order of their submission. We try to accept the earliest one that is closest to being ready to merge.

---

## Common tasks

### Add a redirect

Create a new entry in the [`redirects.js`](https://github.com/supabase/supabase/blob/master/apps/www/lib/redirects.js) file in our main site.

---

### Federated docs

We support "federating" docs, meaning doc content can come directly from external repos other than [`supabase/supabase`](https://github.com/supabase/supabase).

- It's great for things like client libs who have their own set of docs that we don't want to duplicate on the official Supabase docs (eg. [`supabase/vecs`](https://github.com/supabase/vecs)).
- No duplication or manual steps required - fetches and generates automatically as part of the docs build pipeline.
- It's flexible - you can "embed" external docs nearly anywhere at any level in Supabase docs, but they will feel native.
- If you are maintaining a repo containing docs that you think could also live in Supabase docs, feel free to create an issue and we can work together to integrate.

Federated docs work using Next.js's build pipeline. We use `getStaticProps()` to fetch remote documentation (ie. markdown) at build time which is processed and passed to the respective page within the docs.

See the [Vecs Python source code](https://github.com/supabase/supabase/tree/master/apps/docs/app/guides/ai/python/%5Bslug%5D to see how we do this for [`supabase/vecs`](https://github.com/supabase/vecs). Use this as a starting point for federating other docs.

Some things to consider:

- Links will often need to be transformed. For example if you are bringing in external markdown content, they may contain relative links that may not translate 1-to-1 after rendering in the Supabase docs. Use the [Link Transform](https://github.com/supabase/supabase/blob/master/apps/docs/lib/mdx/plugins/rehypeLinkTransform.ts) rehype plugin to transform links.
- External markdown may contain syntax extensions that Supabase docs don't understand by default (eg. [mkdocs-material extensions](https://squidfunk.github.io/mkdocs-material/setup/extensions/python-markdown)). We've built a few remark plugins to support these extensions (eg. [MkDocs Admonition](https://github.com/supabase/supabase/blob/master/apps/docs/lib/mdx/plugins/remarkAdmonition.ts)). If there is a markdown extension that you need that isn't built yet, feel free to open an issue and we can work together to create it.

---

## Community channels

If you get stuck somewhere or have any questions, join our [Discord Community Server](https://discord.supabase.com/) or the [GitHub Discussions](https://github.com/supabase/supabase/discussions). We are here to help!

## Contributors

<a href="https://github.com/supabase/supabase/graphs/contributors">
   <img src="https://contributors.deno.dev/supabase/supabase?height=1200&width=1200&count=90" width="1200" height="1200" alt="contributors">
</a>


## Links discovered
- [Supabase](https://supabase.com)
- [code of conduct](https://github.com/supabase/.github/blob/main/CODE_OF_CONDUCT.md)
- [issues](https://github.com/supabase/supabase/issues)
- [Git](https://git-scm.com/)
- [Node.js v22.x or higher](https://nodejs.org)
- [pnpm](https://pnpm.io/)
- [make](https://www.gnu.org/software/make/)
- [Docker](https://docs.docker.com/get-docker/)
- [Turborepo](https://turborepo.org/docs)
- [Supabase repo](https://github.com/supabase/supabase)
- [supabase.com](https://supabase.com)
- [supabase.com/dashboard](https://supabase.com/dashboard)
- [supabase.com/docs](https://supabase.com/docs)
- [here](https://docs.docker.com/get-docker/)
- [contributor chart](https://github.com/supabase/supabase/graphs/contributors)
- [`redirects.js`](https://github.com/supabase/supabase/blob/master/apps/www/lib/redirects.js)
- [`supabase/supabase`](https://github.com/supabase/supabase)
- [`supabase/vecs`](https://github.com/supabase/vecs)
- [Link Transform](https://github.com/supabase/supabase/blob/master/apps/docs/lib/mdx/plugins/rehypeLinkTransform.ts)
- [mkdocs-material extensions](https://squidfunk.github.io/mkdocs-material/setup/extensions/python-markdown)
- [MkDocs Admonition](https://github.com/supabase/supabase/blob/master/apps/docs/lib/mdx/plugins/remarkAdmonition.ts)
- [Discord Community Server](https://discord.supabase.com/)
- [GitHub Discussions](https://github.com/supabase/supabase/discussions)
- [<img src="https://contributors.deno.dev/supabase/supabase?height=1200&width=1200&count=90" width="1200" height="1200" alt="contributors">](https://github.com/supabase/supabase/graphs/contributors)

--- README.md ---
<p align="center">
<img src="https://user-images.githubusercontent.com/8291514/213727234-cda046d6-28c6-491a-b284-b86c5cede25d.png#gh-light-mode-only">
<img src="https://user-images.githubusercontent.com/8291514/213727225-56186826-bee8-43b5-9b15-86e839d89393.png#gh-dark-mode-only">
</p>

# Supabase

[Supabase](https://supabase.com) is the Postgres development platform. We're building the features of Firebase using enterprise-grade open source tools.

- [x] Hosted Postgres Database. [Docs](https://supabase.com/docs/guides/database)
- [x] Authentication and Authorization. [Docs](https://supabase.com/docs/guides/auth)
- [x] Auto-generated APIs.
  - [x] REST. [Docs](https://supabase.com/docs/guides/api)
  - [x] GraphQL. [Docs](https://supabase.com/docs/guides/graphql)
  - [x] Realtime subscriptions. [Docs](https://supabase.com/docs/guides/realtime)
- [x] Functions.
  - [x] Database Functions. [Docs](https://supabase.com/docs/guides/database/functions)
  - [x] Edge Functions [Docs](https://supabase.com/docs/guides/functions)
- [x] File Storage. [Docs](https://supabase.com/docs/guides/storage)
- [x] AI + Vector/Embeddings Toolkit. [Docs](https://supabase.com/docs/guides/ai)
- [x] Dashboard

![Supabase Dashboard](https://raw.githubusercontent.com/supabase/supabase/master/apps/www/public/images/github/supabase-dashboard.png)

Watch "releases" of this repo to get notified of major updates.

<kbd><img src="https://raw.githubusercontent.com/supabase/supabase/d5f7f413ab356dc1a92075cb3cee4e40a957d5b1/web/static/watch-repo.gif" alt="Watch this repo"/></kbd>

## Documentation

For full documentation, visit [supabase.com/docs](https://supabase.com/docs)

To see how to Contribute, visit [Getting Started](./DEVELOPERS.md)

## Community & Support

- [Community Forum](https://github.com/supabase/supabase/discussions). Best for: help with building, discussion about database best practices.
- [GitHub Issues](https://github.com/supabase/supabase/issues). Best for: bugs and errors you encounter using Supabase.
- [Email Support](https://supabase.com/docs/support#business-support). Best for: problems with your database or infrastructure.
- [Discord](https://discord.supabase.com). Best for: sharing your applications and hanging out with the community.

## How it works

Supabase is a combination of open source tools. We’re building the features of Firebase using enterprise-grade, open source products. If the tools and communities exist, with an MIT, Apache 2, or equivalent open license, we will use and support that tool. If the tool doesn't exist, we build and open source it ourselves. Supabase is not a 1-to-1 mapping of Firebase. Our aim is to give developers a Firebase-like developer experience using open source tools.

**Architecture**

Supabase is a [hosted platform](https://supabase.com/dashboard). You can sign up and start using Supabase without installing anything.
You can also [self-host](https://supabase.com/docs/guides/hosting/overview) and [develop locally](https://supabase.com/docs/guides/local-development).

![Architecture](apps/docs/public/img/supabase-architecture.svg)

- [Postgres](https://www.postgresql.org/) is an object-relational database system with over 30 years of active development that has earned it a strong reputation for reliability, feature robustness, and performance.
- [Realtime](https://github.com/supabase/realtime) is an Elixir server that allows you to listen to PostgreSQL inserts, updates, and deletes using websockets. Realtime polls Postgres' built-in replication functionality for database changes, converts changes to JSON, then broadcasts the JSON over websockets to authorized clients.
- [PostgREST](http://postgrest.org/) is a web server that turns your PostgreSQL database directly into a RESTful API.
- [GoTrue](https://github.com/supabase/gotrue) is a JWT-based authentication API that simplifies user sign-ups, logins, and session management in your applications.
- [Storage](https://github.com/supabase/storage-api) a RESTful API for managing files in S3, with Postgres handling permissions.
- [pg_graphql](http://github.com/supabase/pg_graphql/) a PostgreSQL extension that exposes a GraphQL API.
- [postgres-meta](https://github.com/supabase/postgres-meta) is a RESTful API for managing your Postgres, allowing you to fetch tables, add roles, and run queries, etc.
- [Kong](https://github.com/Kong/kong) is a cloud-native API gateway.

#### Client libraries

Our approach for client libraries is modular. Each sub-library is a standalone implementation for a single external system. This is one of the ways we support existing tools.

<table style="table-layout:fixed; white-space: nowrap;">
  <tr>
    <th>Language</th>
    <th>Client</th>
    <th colspan="5">Feature-Clients (bundled in Supabase client)</th>
  </tr>
  <!-- notranslate -->
  <tr>
    <th></th>
    <th>Supabase</th>
    <th><a href="https://github.com/postgrest/postgrest" target="_blank" rel="noopener noreferrer">PostgREST</a></th>
    <th><a href="https://github.com/supabase/gotrue" target="_blank" rel="noopener noreferrer">GoTrue</a></th>
    <th><a href="https://github.com/supabase/realtime" target="_blank" rel="noopener noreferrer">Realtime</a></th>
    <th><a href="https://github.com/supabase/storage-api" target="_blank" rel="noopener noreferrer">Storage</a></th>
    <th>Functions</th>
  </tr>
  <!-- TEMPLATE FOR NEW ROW -->
  <!-- START ROW
  <tr>
    <td>lang</td>
    <td><a href="https://github.com/supabase-community/supabase-lang" target="_blank" rel="noopener noreferrer">supabase-lang</a></td>
    <td><a href="https://github.com/supabase-community/postgrest-lang" target="_blank" rel="noopener noreferrer">postgrest-lang</a></td>
    <td><a href="https://github.com/supabase-community/gotrue-lang" target="_blank" rel="noopener noreferrer">gotrue-lang</a></td>
    <td><a href="https://github.com/supabase-community/realtime-lang" target="_blank" rel="noopener noreferrer">realtime-lang</a></td>
    <td><a href="https://github.com/supabase-community/storage-lang" target="_blank" rel="noopener noreferrer">storage-lang</a></td>
  </tr>
  END ROW -->
  <!-- /notranslate -->
  <th colspan="7">⚡️ Official ⚡️</th>
  <!-- notranslate -->
  <tr>
    <td>JavaScript (TypeScript)</td>
    <td><a href="https://github.com/supabase/supabase-js" target="_blank" rel="noopener noreferrer">supabase-js</a></td>
    <td><a href="https://github.com/supabase/supabase-js/tree/master/packages/core/postgrest-js" target="_blank" rel="noopener noreferrer">postgrest-js</a></td>
    <td><a href="https://github.com/supabase/supabase-js/tree/master/packages/core/auth-js" target="_blank" rel="noopener noreferrer">auth-js</a></td>
    <td><a href="https://github.com/supabase/supabase-js/tree/master/packages/core/realtime-js" target="_blank" rel="noopener noreferrer">realtime-js</a></td>
    <td><a href="https://github.com/supabase/supabase-js/tree/master/packages/core/storage-js" target="_blank" rel="noopener noreferrer">storage-js</a></td>
    <td><a href="https://github.com/supabase/supabase-js/tree/master/packages/core/functions-js" target="_blank" rel="noopener noreferrer">functions-js</a></td>
  </tr>
    <tr>
    <td>Flutter</td>
    <td><a href="https://github.com/supabase/supabase-flutter" target="_blank" rel="noopener noreferrer">supabase-flutter</a></td>
    <td><a href="https://github.com/supabase/postgrest-dart" target="_blank" rel="noopener noreferrer">postgrest-dart</a></td>
    <td><a href="https://github.com/supabase/gotrue-dart" target="_blank" rel="noopener noreferrer">gotrue-dart</a></td>
    <td><a href="https://github.com/supabase/realtime-dart" target="_blank" rel="noopener noreferrer">realtime-dart</a></td>
    <td><a href="https://github.com/supabase/storage-dart" target="_blank" rel="noopener noreferrer">storage-dart</a></td>
    <td><a href="https://github.com/supabase/functions-dart" target="_blank" rel="noopener noreferrer">functions-dart</a></td>
  </tr>
  <tr>
    <td>Swift</td>
    <td><a href="https://github.com/supabase/supabase-swift" target="_blank" rel="noopener noreferrer">supabase-swift</a></td>
    <td><a href="https://github.com/supabase/supabase-swift/tree/main/Sources/PostgREST" target="_blank" rel="noopener noreferrer">postgrest-swift</a></td>
    <td><a href="https://github.com/supabase/supabase-swift/tree/main/Sources/Auth" target="_blank" rel="noopener noreferrer">auth-swift</a></td>
    <td><a href="https://github.com/supabase/supabase-swift/tree/main/Sources/Realtime" target="_blank" rel="noopener noreferrer">realtime-swift</a></td>
    <td><a href="https://github.com/supabase/supabase-swift/tree/main/Sources/Storage" target="_blank" rel="noopener noreferrer">storage-swift</a></td>
    <td><a href="https://github.com/supabase/supabase-swift/tree/main/Sources/Functions" target="_blank" rel="noopener noreferrer">functions-swift</a></td>
  </tr>
  <tr>
    <td>Python</td>
    <td><a href="https://github.com/supabase/supabase-py" target="_blank" rel="noopener noreferrer">supabase-py</a></td>
    <td><a href="https://github.com/supabase/postgrest-py" target="_blank" rel="noopener noreferrer">postgrest-py</a></td>
    <td><a href="https://github.com/supabase/gotrue-py" target="_blank" rel="noopener noreferrer">gotrue-py</a></td>
    <td><a href="https://github.com/supabase/realtime-py" target="_blank" rel="noopener noreferrer">realtime-py</a></td>
    <td><a href="https://github.com/supabase/storage-py" target="_blank" rel="noopener noreferrer">storage-py</a></td>
    <td><a href="https://github.com/supabase/functions-py" target="_blank" rel="noopener noreferrer">functions-py</a></td>
  </tr>
  <!-- /notranslate -->
  <th colspan="7">💚 Community 💚</th>
  <!-- notranslate -->
  <tr>
    <td>C#</td>
    <td><a href="https://github.com/supabase-community/supabase-csharp" target="_blank" rel="noopener noreferrer">supabase-csharp</a></td>
    <td><a href="https://github.com/supabase-community/postgrest-csharp" target="_blank" rel="noopener noreferrer">postgrest-csharp</a></td>
    <td><a href="https://github.com/supabase-community/gotrue-csharp" target="_blank" rel="noopener noreferrer">gotrue-csharp</a></td>
    <td><a href="https://github.com/supabase-community/realtime-csharp" target="_blank" rel="noopener noreferrer">realtime-csharp</a></td>
    <td><a href="https://github.com/supabase-community/storage-csharp" target="_blank" rel="noopener noreferrer">storage-csharp</a></td>
    <td><a href="https://github.com/supabase-community/functions-csharp" target="_blank" rel="noopener noreferrer">functions-csharp</a></td>
  </tr>
  <tr>
    <td>Go</td>
    <td>-</td>
    <td><a href="https://github.com/supabase-community/postgrest-go" target="_blank" rel="noopener noreferrer">postgrest-go</a></td>
    <td><a href="https://github.com/supabase-community/gotrue-go" target="_blank" rel="noopener noreferrer">gotrue-go</a></td>
    <td>-</td>
    <td><a href="https://github.com/supabase-community/storage-go" target="_blank" rel="noopener noreferrer">storage-go</a></td>
    <td><a href="https://github.com/supabase-community/functions-go" target="_blank" rel="noopener noreferrer">functions-go</a></td>
  </tr>
  <tr>
    <td>Java</td>
    <td>-</td>
    <td>-</td>
    <td><a href="https://github.com/supabase-community/gotrue-java" target="_blank" rel="noopener noreferrer">gotrue-java</a></td>
    <td>-</td>
    <td><a href="https://github.com/supabase-community/storage-java" target="_blank" rel="noopener noreferrer">storage-java</a></td>
    <td>-</td>
  </tr>
  <tr>
    <td>Kotlin</td>
    <td><a href="https://github.com/supabase-community/supabase-kt" target="_blank" rel="noopener noreferrer">supabase-kt</a></td>
    <td><a href="https://github.com/supabase-community/supabase-kt/tree/master/Postgrest" target="_blank" rel="noopener noreferrer">postgrest-kt</a></td>
    <td><a href="https://github.com/supabase-community/supabase-kt/tree/master/Auth" target="_blank" rel="noopener noreferrer">auth-kt</a></td>
    <td><a href="https://github.com/supabase-community/supabase-kt/tree/master/Realtime" target="_blank" rel="noopener noreferrer">realtime-kt</a></td>
    <td><a href="https://github.com/supabase-community/supabase-kt/tree/master/Storage" target="_blank" rel="noopener noreferrer">storage-kt</a></td>
    <td><a href="https://github.com/supabase-community/supabase-kt/tree/master/Functions" target="_blank" rel="noopener noreferrer">functions-kt</a></td>
  </tr>
  <tr>
    <td>Ruby</td>
    <td><a href="https://github.com/supabase-community/supabase-rb" target="_blank" rel="noopener noreferrer">supabase-rb</a></td>
    <td><a href="https://github.com/supabase-community/postgrest-rb" target="_blank" rel="noopener noreferrer">postgrest-rb</a></td>
    <td>-</td>
    <td>-</td>
    <td>-</td>
    <td>-</td>
  </tr>
  <tr>
    <td>Rust</td>
    <td>-</td>
    <td><a href="https://github.com/supabase-community/postgrest-rs" target="_blank" rel="noopener noreferrer">postgrest-rs</a></td>
    <td>-</td>
    <td>-</td>
    <td>-</td>
    <td>-</td>
  </tr>
  <tr>
    <td>Godot Engine (GDScript)</td>
    <td><a href="https://github.com/supabase-community/godot-engine.supabase" target="_blank" rel="noopener noreferrer">supabase-gdscript</a></td>
    <td>-</td>
    <td>-</td>
    <td>-</td>
    <td>-</td>
    <td>-</td>
  </tr>
  <!-- /notranslate -->
</table>

<!--- Remove this list if you're translating to another language, it's hard to keep updated across multiple files-->
<!--- Keep only the link to the list of translation files-->

## Badges

![Made with Supabase](./apps/www/public/badge-made-with-supabase.svg)

```md
[![Made with Supabase](https://supabase.com/badge-made-with-supabase.svg)](https://supabase.com)
```

```html
<a href="https://supabase.com">
  <img
    width="168"
    height="30"
    src="https://supabase.com/badge-made-with-supabase.svg"
    alt="Made with Supabase"
  />
</a>
```

![Made with Supabase (dark)](./apps/www/public/badge-made-with-supabase-dark.svg)

```md
[![Made with Supabase](https://supabase.com/badge-made-with-supabase-dark.svg)](https://supabase.com)
```

```html
<a href="https://supabase.com">
  <img
    width="168"
    height="30"
    src="https://supabase.com/badge-made-with-supabase-dark.svg"
    alt="Made with Supabase"
  />
</a>
```

## Translations

- [Arabic | العربية](/i18n/README.ar.md)
- [Albanian / Shqip](/i18n/README.sq.md)
- [Bangla / বাংলা](/i18n/README.bn.md)
- [Bulgarian / Български](/i18n/README.bg.md)
- [Catalan / Català](/i18n/README.ca.md)
- [Croatian / Hrvatski](/i18n/README.hr.md)
- [Czech / čeština](/i18n/README.cs.md)
- [Danish / Dansk](/i18n/README.da.md)
- [Dutch / Nederlands](/i18n/README.nl.md)
- [English](https://github.com/supabase/supabase)
- [Estonian / eesti keel](/i18n/README.et.md)
- [Finnish / Suomalainen](/i18n/README.fi.md)
- [French / Français](/i18n/README.fr.md)
- [German / Deutsch](/i18n/README.de.md)
- [Greek / Ελληνικά](/i18n/README.el.md)
- [Gujarati / ગુજરાતી](/i18n/README.gu.md)
- [Hebrew / עברית](/i18n/README.he.md)
- [Hindi / हिंदी](/i18n/README.hi.md)
- [Hungarian / Magyar](/i18n/README.hu.md)
- [Nepali / नेपाली](/i18n/README.ne.md)
- [Indonesian / Bahasa Indonesia](/i18n/README.id.md)
- [Italiano / Italian](/i18n/README.it.md)
- [Japanese / 日本語](/i18n/README.jp.md)
- [Korean / 한국어](/i18n/README.ko.md)
- [Lithuanian / lietuvių](/i18n/README.lt.md)
- [Latvian / latviski](/i18n/README.lv.md)
- [Malay / Bahasa Malaysia](/i18n/README.ms.md)
- [Norwegian (Bokmål) / Norsk (Bokmål)](/i18n/README.nb.md)
- [Persian / فارسی](/i18n/README.fa.md)
- [Polish / Polski](/i18n/README.pl.md)
- [Portuguese / Português](/i18n/README.pt.md)
- [Portuguese (Brazilian) / Português Brasileiro](/i18n/README.pt-br.md)
- [Romanian / Română](/i18n/README.ro.md)
- [Russian / Pусский](/i18n/README.ru.md)
- [Serbian / Srpski](/i18n/README.sr.md)
- [Sinhala / සිංහල](/i18n/README.si.md)
- [Slovak / slovenský](/i18n/README.sk.md)
- [Slovenian / Slovenščina](/i18n/README.sl.md)
- [Spanish / Español](/i18n/README.es.md)
- [Simplified Chinese / 简体中文](/i18n/README.zh-cn.md)
- [Swedish / Svenska](/i18n/README.sv.md)
- [Thai / ไทย](/i18n/README.th.md)
- [Traditional Chinese / 繁體中文](/i18n/README.zh-tw.md)
- [Turkish / Türkçe](/i18n/README.tr.md)
- [Ukrainian / Українська](/i18n/README.uk.md)
- [Vietnamese / Tiếng Việt](/i18n/README.vi-vn.md)
- [List of translations](/i18n/languages.md) <!--- Keep only this -->


## Links discovered
- [Supabase](https://supabase.com)
- [Docs](https://supabase.com/docs/guides/database)
- [Docs](https://supabase.com/docs/guides/auth)
- [Docs](https://supabase.com/docs/guides/api)
- [Docs](https://supabase.com/docs/guides/graphql)
- [Docs](https://supabase.com/docs/guides/realtime)
- [Docs](https://supabase.com/docs/guides/database/functions)
- [Docs](https://supabase.com/docs/guides/functions)
- [Docs](https://supabase.com/docs/guides/storage)
- [Docs](https://supabase.com/docs/guides/ai)
- [Supabase Dashboard](https://raw.githubusercontent.com/supabase/supabase/master/apps/www/public/images/github/supabase-dashboard.png)
- [supabase.com/docs](https://supabase.com/docs)
- [Getting Started](https://github.com/supabase/supabase/blob/master/DEVELOPERS.md)
- [Community Forum](https://github.com/supabase/supabase/discussions)
- [GitHub Issues](https://github.com/supabase/supabase/issues)
- [Email Support](https://supabase.com/docs/support#business-support)
- [Discord](https://discord.supabase.com)
- [hosted platform](https://supabase.com/dashboard)
- [self-host](https://supabase.com/docs/guides/hosting/overview)
- [develop locally](https://supabase.com/docs/guides/local-development)
- [Architecture](https://github.com/supabase/supabase/blob/master/apps/docs/public/img/supabase-architecture.svg)
- [Postgres](https://www.postgresql.org/)
- [Realtime](https://github.com/supabase/realtime)
- [PostgREST](http://postgrest.org/)
- [GoTrue](https://github.com/supabase/gotrue)
- [Storage](https://github.com/supabase/storage-api)
- [pg_graphql](http://github.com/supabase/pg_graphql/)
- [postgres-meta](https://github.com/supabase/postgres-meta)
- [Kong](https://github.com/Kong/kong)
- [Made with Supabase](https://github.com/supabase/supabase/blob/master/apps/www/public/badge-made-with-supabase.svg)
- [![Made with Supabase](https://supabase.com/badge-made-with-supabase.svg)
- [Made with Supabase (dark)](https://github.com/supabase/supabase/blob/master/apps/www/public/badge-made-with-supabase-dark.svg)
- [![Made with Supabase](https://supabase.com/badge-made-with-supabase-dark.svg)
- [Arabic | العربية](https://github.com/supabase/supabase/blob/master/i18n/README.ar.md)
- [Albanian / Shqip](https://github.com/supabase/supabase/blob/master/i18n/README.sq.md)
- [Bangla / বাংলা](https://github.com/supabase/supabase/blob/master/i18n/README.bn.md)
- [Bulgarian / Български](https://github.com/supabase/supabase/blob/master/i18n/README.bg.md)
- [Catalan / Català](https://github.com/supabase/supabase/blob/master/i18n/README.ca.md)
- [Croatian / Hrvatski](https://github.com/supabase/supabase/blob/master/i18n/README.hr.md)
- [Czech / čeština](https://github.com/supabase/supabase/blob/master/i18n/README.cs.md)
- [Danish / Dansk](https://github.com/supabase/supabase/blob/master/i18n/README.da.md)
- [Dutch / Nederlands](https://github.com/supabase/supabase/blob/master/i18n/README.nl.md)
- [English](https://github.com/supabase/supabase)
- [Estonian / eesti keel](https://github.com/supabase/supabase/blob/master/i18n/README.et.md)
- [Finnish / Suomalainen](https://github.com/supabase/supabase/blob/master/i18n/README.fi.md)
- [French / Français](https://github.com/supabase/supabase/blob/master/i18n/README.fr.md)
- [German / Deutsch](https://github.com/supabase/supabase/blob/master/i18n/README.de.md)
- [Greek / Ελληνικά](https://github.com/supabase/supabase/blob/master/i18n/README.el.md)
- [Gujarati / ગુજરાતી](https://github.com/supabase/supabase/blob/master/i18n/README.gu.md)
- [Hebrew / עברית](https://github.com/supabase/supabase/blob/master/i18n/README.he.md)
- [Hindi / हिंदी](https://github.com/supabase/supabase/blob/master/i18n/README.hi.md)
- [Hungarian / Magyar](https://github.com/supabase/supabase/blob/master/i18n/README.hu.md)
- [Nepali / नेपाली](https://github.com/supabase/supabase/blob/master/i18n/README.ne.md)
- [Indonesian / Bahasa Indonesia](https://github.com/supabase/supabase/blob/master/i18n/README.id.md)
- [Italiano / Italian](https://github.com/supabase/supabase/blob/master/i18n/README.it.md)
- [Japanese / 日本語](https://github.com/supabase/supabase/blob/master/i18n/README.jp.md)
- [Korean / 한국어](https://github.com/supabase/supabase/blob/master/i18n/README.ko.md)
- [Lithuanian / lietuvių](https://github.com/supabase/supabase/blob/master/i18n/README.lt.md)
- [Latvian / latviski](https://github.com/supabase/supabase/blob/master/i18n/README.lv.md)
- [Malay / Bahasa Malaysia](https://github.com/supabase/supabase/blob/master/i18n/README.ms.md)
- [Norwegian (Bokmål) / Norsk (Bokmål)](https://github.com/supabase/supabase/blob/master/i18n/README.nb.md)
- [Persian / فارسی](https://github.com/supabase/supabase/blob/master/i18n/README.fa.md)
- [Polish / Polski](https://github.com/supabase/supabase/blob/master/i18n/README.pl.md)
- [Portuguese / Português](https://github.com/supabase/supabase/blob/master/i18n/README.pt.md)
- [Portuguese (Brazilian) / Português Brasileiro](https://github.com/supabase/supabase/blob/master/i18n/README.pt-br.md)
- [Romanian / Română](https://github.com/supabase/supabase/blob/master/i18n/README.ro.md)
- [Russian / Pусский](https://github.com/supabase/supabase/blob/master/i18n/README.ru.md)
- [Serbian / Srpski](https://github.com/supabase/supabase/blob/master/i18n/README.sr.md)
- [Sinhala / සිංහල](https://github.com/supabase/supabase/blob/master/i18n/README.si.md)
- [Slovak / slovenský](https://github.com/supabase/supabase/blob/master/i18n/README.sk.md)
- [Slovenian / Slovenščina](https://github.com/supabase/supabase/blob/master/i18n/README.sl.md)
- [Spanish / Español](https://github.com/supabase/supabase/blob/master/i18n/README.es.md)
- [Simplified Chinese / 简体中文](https://github.com/supabase/supabase/blob/master/i18n/README.zh-cn.md)
- [Swedish / Svenska](https://github.com/supabase/supabase/blob/master/i18n/README.sv.md)
- [Thai / ไทย](https://github.com/supabase/supabase/blob/master/i18n/README.th.md)
- [Traditional Chinese / 繁體中文](https://github.com/supabase/supabase/blob/master/i18n/README.zh-tw.md)
- [Turkish / Türkçe](https://github.com/supabase/supabase/blob/master/i18n/README.tr.md)
- [Ukrainian / Українська](https://github.com/supabase/supabase/blob/master/i18n/README.uk.md)
- [Vietnamese / Tiếng Việt](https://github.com/supabase/supabase/blob/master/i18n/README.vi-vn.md)
- [List of translations](https://github.com/supabase/supabase/blob/master/i18n/languages.md)
- [PostgREST](https://github.com/postgrest/postgrest)
- [supabase-lang](https://github.com/supabase-community/supabase-lang)
- [postgrest-lang](https://github.com/supabase-community/postgrest-lang)
- [gotrue-lang](https://github.com/supabase-community/gotrue-lang)
- [realtime-lang](https://github.com/supabase-community/realtime-lang)
- [storage-lang](https://github.com/supabase-community/storage-lang)
- [supabase-js](https://github.com/supabase/supabase-js)
- [postgrest-js](https://github.com/supabase/supabase-js/tree/master/packages/core/postgrest-js)
- [auth-js](https://github.com/supabase/supabase-js/tree/master/packages/core/auth-js)
- [realtime-js](https://github.com/supabase/supabase-js/tree/master/packages/core/realtime-js)
- [storage-js](https://github.com/supabase/supabase-js/tree/master/packages/core/storage-js)
- [functions-js](https://github.com/supabase/supabase-js/tree/master/packages/core/functions-js)
- [supabase-flutter](https://github.com/supabase/supabase-flutter)
- [postgrest-dart](https://github.com/supabase/postgrest-dart)
- [gotrue-dart](https://github.com/supabase/gotrue-dart)
- [realtime-dart](https://github.com/supabase/realtime-dart)
- [storage-dart](https://github.com/supabase/storage-dart)
- [functions-dart](https://github.com/supabase/functions-dart)
- [supabase-swift](https://github.com/supabase/supabase-swift)
- [postgrest-swift](https://github.com/supabase/supabase-swift/tree/main/Sources/PostgREST)

--- apps/cms/README.md ---
# Payload CMS

### Local Development

1. run `cd apps/cms && supabase start` to start the local supabase project
2. run `cp .env.example .env` to copy the example environment variables and update the variables. You'll need to add the `S3_` variables to your `.env` to use Supabase Storage
3. `pnpm install && pnpm generate:importmap` to install dependencies and start the dev server
4. run `pnpm dev` in the apps/cms folder or `pnpm dev:cms` from the root
5. open `http://localhost:3030` to open the app in your browser

Follow the on-screen instructions to login and create the first admin user.

### Collections

Collections are what data looks like in the Payload cms schema. The following are the collections currently configured in the app.

- Authors
- Categories
- Events
- Media
- Posts
- Tags
- Users


--- apps/studio/README.md ---
# Supabase Studio

A dashboard for managing your self-hosted Supabase project, and used on our [hosted platform](https://supabase.com/dashboard). Built with:

- [Next.js](https://nextjs.org/)
- [Tailwind](https://tailwindcss.com/)

## What's included

Studio is designed to work with existing deployments - either the local hosted, docker setup, or our CLI. It is not intended for managing the deployment and administration of projects - that's out of scope.

As such, the features exposed on Studio for existing deployments are limited to those which manage your database:

- Table & SQL editors
  - Saved queries are unavailable
- Database management
  - Policies, roles, extensions, replication
- API documentation

## Managing Project Settings

Project settings are managed outside of the Dashboard. If you use docker compose, you should manage the settings in your docker-compose file. If you're deploying Supabase to your own cloud, you should store your secrets and env vars in a vault or secrets manager.

## How to contribute?

- Branch from `master` and name your branches with the following structure
  - `{type}/{branch_name}`
    - Type: `chore | fix | feature`
    - The branch name is arbitrary — just make sure it summarizes the work.
- When you send a PR to `master`, it will automatically tag members of the frontend team for review.
- Review the [contributing checklists](contributing/contributing-checklists.md) to help test your feature before sending a PR.
- The Dashboard is under active development. You should run `git pull` frequently to make sure you're up to date.

### Developer Quickstart

> [!NOTE]  
> **Supabase internal use:** To develop on Studio locally with the backend services, see the instructions in the [internal `infrastructure` repo](https://github.com/supabase/platform/blob/develop/docs/contributing.md).

```bash
# You'll need to be on Node v20
# in /studio

## For external contributors
pnpm install # install dependencies
pnpm run dev # start dev server

## For internal contributors
## First clone the private supabase/platform repo and follow instructions for setting up mise
mise studio  # Run from supabase/platform alongside `mise infra`

## For all
pnpm run test # run tests
pnpm run test -- --watch # run tests in watch mode
```

## Running within a self-hosted environment

Follow the [self-hosting guide](https://supabase.com/docs/guides/hosting/docker) to get started.

```
cd ..
cd docker
docker compose -f docker-compose.yml -f ./dev/docker-compose.dev.yml up
```

Once you've got that set up, update `.env` in the studio folder with the corresponding values.

```
POSTGRES_PASSWORD=
SUPABASE_ANON_KEY=
SUPABASE_SERVICE_KEY=
```

Then run the following commands to install dependencies and start the dashboard.

```
npm install
npm run dev
```

If you would like to configure different defaults for "Default Organization" and "Default Project", you will need to update the `.env` in the studio folder with the corresponding values.

```
DEFAULT_ORGANIZATION_NAME=
DEFAULT_PROJECT_NAME=
```


## Links discovered
- [hosted platform](https://supabase.com/dashboard)
- [Next.js](https://nextjs.org/)
- [Tailwind](https://tailwindcss.com/)
- [contributing checklists](https://github.com/supabase/supabase/blob/master/apps/studio/contributing/contributing-checklists.md)
- [internal `infrastructure` repo](https://github.com/supabase/platform/blob/develop/docs/contributing.md)
- [self-hosting guide](https://supabase.com/docs/guides/hosting/docker)

--- apps/ui-library/README.md ---
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).

## Getting Started

First, run the development server:

```bash
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
```

Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.

You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.

This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.

## Learn More

To learn more about Next.js, take a look at the following resources:

- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.

You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!

## Deploy on Vercel

The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.

Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.

## Supabase types

To regenerate the Supabase database types, run

```
supabase gen types --local > registry/default/fixtures/database.types.ts
```


## Links discovered
- [Next.js](https://nextjs.org/)
- [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app)
- [http://localhost:3000](http://localhost:3000)
- [`next/font`](https://nextjs.org/docs/basic-features/font-optimization)
- [Next.js Documentation](https://nextjs.org/docs)
- [Learn Next.js](https://nextjs.org/learn)
- [the Next.js GitHub repository](https://github.com/vercel/next.js/)
- [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme)
- [Next.js deployment documentation](https://nextjs.org/docs/deployment)

--- apps/www/README.md ---
# supabase.com

## Overview

Refer to the [Development Guide](../../DEVELOPERS.md) to learn how to run this site locally.

To get started copy the example env file using `cp .env.local.example .env.local`.


## Links discovered
- [Development Guide](https://github.com/supabase/supabase/blob/master/DEVELOPERS.md)

--- apps/studio/components/README.md ---
# Writing components

## Where to create your components

- For components that declare the general structure and layout of a page:
  - `/components/layouts/xxx`
- For components that are tightly coupled to a specific interface:
  - `/components/interfaces/xxx`
- For components that are meant to be reusable across multiple pages:
  - `/components/ui/xxx`
- Note: We're gradually moving files out of the `to-be-cleaned` folder into the respective folders as we refactor

## Component structure

- If a component has constants and utility methods that are tightly coupled to itself, keep them close to the component and enclose them in a folder with an `index.tsx` as an entry point
- Otherwise it can just be a file on its own
- For example:
  - ```
    components/ui
    - SampleComponentA
      - SampleComponentA.tsx
      - SampleComponentA.constants.ts
      - SampleComponentA.utils.ts
      - SampleComponentA.types.ts
      - index.ts
    - SampleComponentB.tsx
    ```

## Template for building components

```ts

// Declare the prop types of your component
interface ComponentAProps {
  sampleProp: string
}

// Name your component accordingly
const ComponentA = ({ sampleProp }: ComponentAProps) => {
  return <div>ComponentA: {sampleProp}</div>
}

export default ComponentA
```


--- apps/studio/pages/README.md ---
# Writing pages

## Rough guidelines

- Try to break down your pages into smaller building blocks - components which are tightly coupled to a page can be placed within the folder `components/interfaces/xxx/...` (Refer to the README.md under the components folder)
- Keep to using `useState` hooks for any UI related logic, do not create MobX local stores to handle UI logic.

## Template for building pages

```tsx
import { NextPage } from 'next'
import { withAuth } from 'hooks/misc/withAuth'

// Import the corresponding layout based on the page
import { Layout } from 'components/layouts'

// Import the main building blocks of the page
import { ... } from 'components/interfaces/xxx'

// Import reusable UI components if needed
import { ... } from 'components/ui/xxx'

// Name your page accordingly
const Page: NextPage = () => {

  return (
    <Layout>
      <div>Page content</div>
    </Layout>
  )
}

export default withAuth(Page)
```


--- apps/studio/styles/README.md ---
# Styles

## When to write custom styles

Tailwind should be sufficient to cover the majority of styling needs. We typically only write custom styles here in the event where we're working with an external library and we need to override some styles (e.g Monaco, or even our own UI library).

Ideally, keep custom styling here to a minimum, use tailwind directly in the pages and components where possible, so that we have less code to maintain.

## If you're writing custom styles

Group custom styles into separate stylesheets based on their context. For styles which are generic and global, we can write them in `main.scss`.


--- apps/studio/tests/README.md ---
# UI Testing Notes

## Rules

- All tests should be run consistently (avoid situations whereby tests fails "sometimes")

- Group tests in folders based on the feature they are testing. Avoid file/folder based folder names since those can change and we will forget to update the tests.

Examples: /logs /reports /projects /database-settings /auth

## Custom Render and Custom Render Hook

`customRender` and `customRenderHook` are wrappers around `render` and `renderHook` that add some necessary providers like `QueryClientProvider`, `TooltipProvider` and `NuqsTestingAdapter`.

Generally use those instead of the default `render` and `renderHook` functions.

```ts
import { customRender, customRenderHook } from 'tests/lib/custom-render'

customRender(<MyComponent />)
customRenderHook(() => useMyHook())
```

## Mocking API Requests

To mock API requests, we use the `msw` library.

Global mocks can be found in `tests/lib/msw-global-api-mocks.ts`.

To mock an endpoint you can use the `addAPIMock` function. Make sure to add the mock in the `beforeEach` hook. It won't work with `beforeAll` if you have many tests.

```ts
beforeEach(() => {
  addAPIMock({
    method: 'get',
    path: '/api/my-endpoint',
    response: {
      data: { foo: 'bar' },
    },
  })
})
```

### API Mocking Tips:

- Keep mocks in the same folder as the tests that use them
- Add a test to verify the mock is working

This will make debugging and updating the mocks easier.

```ts
test('mock is working', async () => {
  const response = await fetch('/api/my-endpoint')
  expect(response.json()).resolves.toEqual({ data: { foo: 'bar' } })
})
```

## Mocking Nuqs URL Parameters

To render a component that uses Nuqs with some predefined query parameters, you can use `customRender` with the `nuqs` prop.

```ts

customRender(<MyComponent />, {
  nuqs: {
    searchParams: {
      search: 'hello world',
    },
  },
})
```

## `<Popover>` vs `<Dropdown>`

When simulating clicks on these components, do the following:

```js
// for Popovers
import userEvent from '@testing-library/user-event'
await userEvent.click('Hello world')

// for Dropdowns
import clickDropdown from 'tests/helpers'
clickDropdown('Hello world')
```


--- apps/studio/data/__templates/README.md ---
# Templates for Tanstack Query queries & mutations

## Usage

1. Duplicate the files in the \_\_templates directory you need depending on your use case.
2. Rename the files to match your resource name.
3. Update the `resource` variables to match your resource name. Usually find and replace works great here if you use case sensitive search (`resource` then `Resource`)
4. Implement your `queryFn` function.

Referencing the other files in the `data` directory is a good way to see how to use the library.

### SQL queries and mutations

The dashboard often needs to query the users database directly. You can use the `executeSql()` function to do this.

## Files

### `resources-query.ts`

For queries that return a list of results.

> :warning: Should only be used for simple lists, as it does not include any pagination. `useInfiniteQuery` should be used instead.

### `resource-query.ts`

For getting a single item based on parameter(s). `id` is used in this example but can be updated to whatever is needed.

### `resource-update-mutation.ts`

For updating a resource. This can easily be adapted to work for create, delete, or any verb that is needed. You may need to modify the `invalidateQueries` section depending on your use case. For example removing:

```ts
queryClient.invalidateQueries({ queryKey: resourceKeys.resource(projectRef, id) })
```

if you are creating a new resource.

### `keys.ts`

Standardized keys for use with `useQuery` and `invalidateQueries`. This is not required but is a good way to keep things consistent.

## Learning Resources

- [Tanstack Query Docs](https://tanstack.com/query/v4/docs/react/overview)
- [TkDodo's Blog Posts](https://tanstack.com/query/v4/docs/react/community/tkdodos-blog)

The blog posts in particular are very helpful for learning best practices and how to use the library effectively.

## UI Error handling

**All network requests** should be done through queries or mutations as they provide a couple of helpful parameters that can ensure our UI covers all scenarios. The main purpose is so that users are always informed whenever a network request fails, rather than allowing the requests to fail silently in the background and the users are left guessing as to what is happening.

### Queries

The 3 states that are provided in a query (`isLoading`, `isError`, `isSuccess`) **must** be covered in the UI whenever we're using it (unless reasonably unrequired). Typically, we'd use the following UI components to cover them, and the states are mutually exclusive so it's preferred not to nest them in a ternary operator for better readability.

```jsx
const { data, error, isLoading, isError, isSuccess } = useQuery()

return (
  <>
    {isLoading && <GenericSkeletonLoader />}

    {isError && <AlertError subject="A subject" error={error} />}

    {isSuccess && <div>Your UI component</div>}
  </>
)
```

### Mutations

The parameter `onError` can be passed when initializing the mutation, where an appropriate UI behavior should be triggered (usually a toast will be fine). If `onError` is not provided, we will then default to a toast message that will be called from within the mutation itself which should handle most cases. However, if there is a specific way that the error should be handled, then this should be passed via the `onError` parameter. (There's no need to repeat the error handling if the default behavior is sufficient)

We should also aim to convert most of the `mutateAsync` to `mutate`, otherwise we'd need to wrap all the `mutateAsync` calls in try catches just to ensure that the client doesn't crash when the request throws an error in the mutation. There's currently a mix due to the Form component that we're using, in particular when a method from the Form component such as `resetForm` needs to be called after the request is completed successfully.

```jsx
const { mutate: someAction } = useMutation({
  onSuccess: (res) => {
    toast.success('Success')
  },
  onError: (error) => {
    toast.error(`Failed: ${error.message}`)
  },
})

const onConfirm = async () => {
  // Assuming that your mutation needs a URL param like project ref
  // This check is just to satisfy the linting - there's an implicit assumption that
  // projectRef here will definitely be available since its obtained from the URL
  if (!projectRef) return console.error('Project ref is required')

  // Any logic before calling the mutation
  someAction({ projectRef, otherParameters })
}
```


## Links discovered
- [Tanstack Query Docs](https://tanstack.com/query/v4/docs/react/overview)
- [TkDodo's Blog Posts](https://tanstack.com/query/v4/docs/react/community/tkdodos-blog)

--- apps/www/lib/cms/README.md ---
# CMS Block Integration

This directory contains utilities for integrating PayloadCMS custom blocks with the www marketing site blog.

## Overview

PayloadCMS blog posts can contain custom blocks (Banner, MediaBlock, Code, Quote, YouTube) in addition to regular text content. This integration converts those blocks into the existing markdown/MDX syntax that the www blog already understands, ensuring seamless compatibility.

## Components

### convertRichTextToMarkdown.ts

Enhanced conversion utilities:

- `convertRichTextToMarkdownWithBlocks()` - Converts PayloadCMS rich text to markdown using existing www syntax
- `convertRichTextToMarkdown()` - Legacy function for backward compatibility

### processCMSContent.ts

Main processor that:

1. Converts PayloadCMS rich text to markdown using existing www component syntax
2. Generates table of contents from the converted content
3. Serializes content for MDX rendering

## Usage

### In Blog Pages

```typescript
import { processCMSContent } from '~/lib/cms/processCMSContent'

// Process CMS content - blocks are converted to existing www syntax
const processedContent = await processCMSContent(cmsPost.content, tocDepth)

// Use in blog data
const blogData = {
  ...cmsPost,
  content: processedContent.content, // MDX with existing www components
  toc: processedContent.toc,
}
```

### Block Conversion Examples

PayloadCMS blocks are automatically converted to existing www markdown syntax:

````typescript
// PayloadCMS MediaBlock becomes:
'<Img alt="Description" src="/uploads/image.jpg" />'

// PayloadCMS Code block becomes:
'```typescript\nconst example = "Hello world"\n```'

// PayloadCMS Quote block becomes:
'<Quote img="avatar.jpg" caption="Author Name">\n\nQuote text here\n\n</Quote>'

// PayloadCMS YouTube block becomes:
'<div className="video-container">\n  <iframe src="..." />\n</div>'

// PayloadCMS Banner block becomes:
'<Admonition type="note">\n\nBanner content\n\n</Admonition>'
````

## Block Type Mapping

| PayloadCMS Block | WWW Syntax                          | Notes                                                                     |
| ---------------- | ----------------------------------- | ------------------------------------------------------------------------- |
| `banner`         | `<Admonition>`                      | Style mapped: info→note, warning→warning, error→destructive, success→note |
| `mediaBlock`     | `<Img>`                             | Handles CMS image URLs and captions                                       |
| `code`           | `code`                              | Standard markdown code blocks with language syntax highlighting           |
| `quote`          | `<Quote>`                           | Avatar image and caption support                                          |
| `youtube`        | `<div className="video-container">` | Wrapped iframe with expected styling                                      |

## Environment Variables

- `NEXT_PUBLIC_CMS_SITE_ORIGIN` - Used for resolving relative CMS image URLs

## Integration Points

1. **apps/www/app/blog/[slug]/page.tsx** - Uses `processCMSContent()` to convert CMS content
2. **apps/www/app/blog/[slug]/BlogPostClient.tsx** - Handles live preview with block conversion
3. **apps/www/lib/get-cms-posts.tsx** - Uses shared conversion utilities for consistent processing

This system allows PayloadCMS editors to use rich blocks while maintaining 100% compatibility with the existing MDX-based blog rendering system. No new components or special handling is required - blocks are seamlessly converted to the markdown/MDX syntax that www already understands.


--- blocks/vue/index.ts ---
import { blocks as originBlocks } from './registry/index'

const blocks = originBlocks.map((item) => {
  const newItem = { ...item }
  newItem.files = newItem.files?.map((file: any) => {
    if (file.path.startsWith('registry/')) {
      return { ...file, path: `node_modules/@supabase/vue-blocks/${file.path}` }
    }
    return file
  })
  return newItem
})

export { blocks }


--- blocks/vue/registry/clients.ts ---
import { type RegistryItem } from 'shadcn/schema'

import nuxtjs from './default/clients/nuxtjs/registry-item.json' with { type: 'json' }
import vue from './default/clients/vue/registry-item.json' with { type: 'json' }

export const clients = [nuxtjs, vue] as RegistryItem[]


--- blocks/vue/registry/index.ts ---
import { clients } from './clients'
import { passwordBasedAuth } from './password-based-auth'
import { socialAuth } from './social-auth'

const blocks = [...clients, ...passwordBasedAuth, ...socialAuth]

export { blocks }


--- blocks/vue/registry/password-based-auth.ts ---
import { type Registry } from 'shadcn/schema'
import nuxtjs from './default/password-based-auth/nuxtjs/registry-item.json' with { type: 'json' }
import vue from './default/password-based-auth/vue/registry-item.json' with { type: 'json' }

export const passwordBasedAuth = [nuxtjs, vue] as Registry['items']


--- blocks/vue/lib/process-registry.ts ---
import * as fs from 'fs'

export interface RegistryNode {
  name: string
  path: string
  originalPath: string
  type: 'directory' | 'file'
  children?: RegistryNode[]
  content?: string
}

interface RegistryFile {
  path: string
  target?: string
  type: string
  content: string
}

const DEFAULT_PATHS = {
  component: '/components',
  hook: '/hooks',
  util: '/lib',
} as const

/**
 * Converts a flat registry array into a hierarchical file tree structure
 */
export function generateRegistryTree(registryPath: string): RegistryNode[] {
  const registry = JSON.parse(fs.readFileSync(registryPath, 'utf-8')) as { files: RegistryFile[] }
  const tree: RegistryNode[] = []

  const sortedRegistry = [...registry.files].sort((a, b) => a.path.localeCompare(b.path))

  for (const file of sortedRegistry) {
    const itemPath = file.target || getDefaultPath(file)
    const pathParts = itemPath.split('/').filter(Boolean)
    let currentLevel = tree

    for (let i = 0; i < pathParts.length; i++) {
      const part = pathParts[i]
      const isLast = i === pathParts.length - 1
      const path = '/' + pathParts.slice(0, i + 1).join('/')

      let node = currentLevel.find((n) => n.name === part)

      // Remove any paths in the file content that point to the block directory.
      const content = file.content
        .replaceAll(/@\/registry\/default\/blocks\/.+?\//gi, '@/')
        .replaceAll(/@\/registry\/default\/fixtures\//gi, '@/')
        .replaceAll(/@\/registry\/default\//gi, '@/')
        .replaceAll(/@\/clients\/.+?\//gi, '@/')

      if (!node) {
        node = {
          name: part,
          path,
          originalPath: file.path,
          type: isLast ? 'file' : 'directory',
          ...(isLast ? { content } : { children: [] }),
        }
        currentLevel.push(node)
      }

      if (!isLast) {
        node.children = node.children || []
        currentLevel = node.children
      }
    }
  }

  return tree
}

/**
 * Determines the default path for an item based on its type
 */
function getDefaultPath(item: RegistryFile): string {
  const type = item.type.toLowerCase() || ''
  const basePath = DEFAULT_PATHS[type as keyof typeof DEFAULT_PATHS] || ''
  // clean all paths that start with paths specific to this repo organization
  const filePath = item.path
    .replace(/registry\/default\/blocks\/.+?\//, '')
    .replace(/registry\/default\/clients\/.+?\//, '')

  return `${basePath}/${filePath}`
}


--- blocks/vue/registry/social-auth.ts ---
import { type Registry } from 'shadcn/schema'
import vue from './default/social-auth/vue/registry-item.json' with { type: 'json' }
import nuxt from './default/social-auth/nuxtjs/registry-item.json' with { type: 'json' }

export const socialAuth = [vue, nuxt] as Registry['items']


--- blocks/vue/registry/default/lib/utils.ts ---
import type { ClassValue } from "clsx"
import { clsx } from "clsx"
import { twMerge } from "tailwind-merge"

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs))
}

--- blocks/vue/registry/default/social-auth/nuxtjs/server/middleware/auth.ts ---
import { defineEventHandler, sendRedirect } from 'h3'
import { createSupabaseServerClient } from '@/registry/default/clients/nuxtjs/server/supabase/client'

export default defineEventHandler(async (event) => {
  const supabase = createSupabaseServerClient(event)

  // Get user claims
  const { data } = await supabase.auth.getClaims()
  const user = data?.claims

  const pathname = event.node.req.url || '/'

  // Redirect if no user and not already on login/auth route
  if (
    !user &&
    !pathname.startsWith('/login') &&
    !pathname.startsWith('/auth')
  ) {
    return sendRedirect(event, '/auth/login')
  }

  // Return event as-is (you could return any object if needed)
  return { user }
})


--- blocks/vue/registry/default/clients/nuxtjs/lib/supabase/client.ts ---
import { createBrowserClient } from '@supabase/ssr'

export function createClient() {
  return createBrowserClient(
    process.env.NUXT_PUBLIC_SUPABASE_URL!,
    process.env.NUXT_PUBLIC_SUPABASE_PUBLISHABLE_OR_ANON_KEY!
  )
}


--- blocks/vue/registry/default/clients/nuxtjs/server/supabase/client.ts ---
import { createServerClient } from '@supabase/ssr'
import { getCookie, setCookie, deleteCookie, H3Event, EventHandlerRequest } from 'h3'

export const createSupabaseServerClient = (event: H3Event<EventHandlerRequest> | undefined) => {
  return createServerClient(
    process.env.NUXT_PUBLIC_SUPABASE_URL!,
    process.env.NUXT_PUBLIC_SUPABASE_PUBLISHABLE_OR_ANON_KEY!,
    {
      cookies: {
        get: (key) => getCookie(event!, key),
        set: (key, value, options) => setCookie(event!, key, value, options),
        remove: (key, options) => deleteCookie(event!, key, options),
      },
    }
  )
}

--- docker/README.md ---
# Self-Hosted Supabase with Docker

This is the official Docker Compose setup for self-hosted Supabase. It provides a complete stack with all Supabase services running locally or on your infrastructure.

## Getting Started

Follow the detailed setup guide in our documentation: [Self-Hosting with Docker](https://supabase.com/docs/guides/self-hosting/docker)

The guide covers:
- Prerequisites (Git and Docker)
- Initial setup and configuration
- Securing your installation
- Accessing services
- Updating your instance

## What's Included

This Docker Compose configuration includes the following services:

- **[Studio](https://github.com/supabase/supabase/tree/master/apps/studio)** - A dashboard for managing your self-hosted Supabase project
- **[Kong](https://github.com/Kong/kong)** - Kong API gateway
- **[Auth](https://github.com/supabase/auth)** - JWT-based authentication API for user sign-ups, logins, and session management
- **[PostgREST](https://github.com/PostgREST/postgrest)** - Web server that turns your PostgreSQL database directly into a RESTful API
- **[Realtime](https://github.com/supabase/realtime)** - Elixir server that listens to PostgreSQL database changes and broadcasts them over websockets
- **[Storage](https://github.com/supabase/storage)** - RESTful API for managing files in S3, with Postgres handling permissions
- **[imgproxy](https://github.com/imgproxy/imgproxy)** - Fast and secure image processing server
- **[postgres-meta](https://github.com/supabase/postgres-meta)** - RESTful API for managing Postgres (fetch tables, add roles, run queries)
- **[PostgreSQL](https://github.com/supabase/postgres)** - Object-relational database with over 30 years of active development
- **[Edge Runtime](https://github.com/supabase/edge-runtime)** - Web server based on Deno runtime for running JavaScript, TypeScript, and WASM services
- **[Logflare](https://github.com/Logflare/logflare)** - Log management and event analytics platform
- **[Vector](https://github.com/vectordotdev/vector)** - High-performance observability data pipeline for logs
- **[Supavisor](https://github.com/supabase/supavisor)** - Supabase's Postgres connection pooler

## Documentation

- **[Documentation](https://supabase.com/docs/guides/self-hosting/docker)** - Setup and configuration guides
- **[CHANGELOG.md](./CHANGELOG.md)** - Track recent updates and changes to services
- **[versions.md](./versions.md)** - Complete history of Docker image versions for rollback reference

## Updates

To update your self-hosted Supabase instance:

1. Review [CHANGELOG.md](./CHANGELOG.md) for breaking changes
2. Check [versions.md](./versions.md) for new image versions
3. Update `docker-compose.yml` if there are configuration changes
4. Pull the latest images: `docker compose pull`
5. Stop services: `docker compose down`
6. Start services with new configuration: `docker compose up -d`

**Note:** Consider to always backup your database before updating.

## Community & Support

For troubleshooting common issues, see:
- [GitHub Discussions](https://github.com/orgs/supabase/discussions?discussions_q=is%3Aopen+label%3Aself-hosted) - Questions, feature requests, and workarounds
- [GitHub Issues](https://github.com/supabase/supabase/issues?q=is%3Aissue%20state%3Aopen%20label%3Aself-hosted) - Known issues
- [Documentation](https://supabase.com/docs/guides/self-hosting) - Setup and configuration guides

Self-hosted Supabase is community-supported. Get help and connect with other users:

- [Discord](https://discord.supabase.com) - Real-time chat and community support
- [Reddit](https://www.reddit.com/r/Supabase/) - Official Supabase subreddit

Share your self-hosting experience:

- [GitHub Discussions](https://github.com/orgs/supabase/discussions/39820) - "Self-hosting: What's working (and what's not)?"

## Important Notes

### Security

⚠️ **The default configuration is not secure for production use.**

Before deploying to production, you must:
- Update all default passwords and secrets in the `.env` file
- Generate new JWT secrets
- Review and update CORS settings
- Consider setting up a secure proxy in front of self-hosted Supabase
- Review and adjust network security configuration (ACLs, etc.)
- Set up proper backup procedures

See the [security section](https://supabase.com/docs/guides/self-hosting/docker#configuring-and-securing-supabase) in the documentation.

## License

This repository is licensed under the Apache 2.0 License. See the main [Supabase repository](https://github.com/supabase/supabase) for details.


## Links discovered
- [Self-Hosting with Docker](https://supabase.com/docs/guides/self-hosting/docker)
- [Studio](https://github.com/supabase/supabase/tree/master/apps/studio)
- [Kong](https://github.com/Kong/kong)
- [Auth](https://github.com/supabase/auth)
- [PostgREST](https://github.com/PostgREST/postgrest)
- [Realtime](https://github.com/supabase/realtime)
- [Storage](https://github.com/supabase/storage)
- [imgproxy](https://github.com/imgproxy/imgproxy)
- [postgres-meta](https://github.com/supabase/postgres-meta)
- [PostgreSQL](https://github.com/supabase/postgres)
- [Edge Runtime](https://github.com/supabase/edge-runtime)
- [Logflare](https://github.com/Logflare/logflare)
- [Vector](https://github.com/vectordotdev/vector)
- [Supavisor](https://github.com/supabase/supavisor)
- [Documentation](https://supabase.com/docs/guides/self-hosting/docker)
- [CHANGELOG.md](https://github.com/supabase/supabase/blob/master/docker/CHANGELOG.md)
- [versions.md](https://github.com/supabase/supabase/blob/master/docker/versions.md)
- [GitHub Discussions](https://github.com/orgs/supabase/discussions?discussions_q=is%3Aopen+label%3Aself-hosted)
- [GitHub Issues](https://github.com/supabase/supabase/issues?q=is%3Aissue%20state%3Aopen%20label%3Aself-hosted)
- [Documentation](https://supabase.com/docs/guides/self-hosting)
- [Discord](https://discord.supabase.com)
- [Reddit](https://www.reddit.com/r/Supabase/)
- [GitHub Discussions](https://github.com/orgs/supabase/discussions/39820)
- [security section](https://supabase.com/docs/guides/self-hosting/docker#configuring-and-securing-supabase)
- [Supabase repository](https://github.com/supabase/supabase)

--- docker/versions.md ---
# Docker Image Versions

## 2025-12-18
- supabase/studio:2025.12.17-sha-43f4f7f (prev supabase/studio:2025.12.09-sha-434634f)
- supabase/gotrue:v2.184.0 (prev supabase/gotrue:v2.183.0)
- supabase/postgres-meta:v0.95.1 (prev supabase/postgres-meta:v0.93.1)
- supabase/logflare:1.27.0 (prev supabase/logflare:1.26.25)

## 2025-12-10
- supabase/studio:2025.12.09-sha-434634f (prev supabase/studio:2025.11.26-sha-8f096b5)
- postgrest/postgrest:v14.1 (prev postgrest/postgrest:v13.0.7)
- supabase/realtime:v2.68.0 (prev supabase/realtime:v2.65.3)
- supabase/storage-api:v1.33.0 (prev supabase/storage-api:v1.32.0)
- supabase/edge-runtime:v1.69.28 (prev supabase/edge-runtime:v1.69.25)
- supabase/logflare:1.26.25 (prev supabase/logflare:1.26.13)

## 2025-11-26
- supabase/studio:2025.11.26-sha-8f096b5 (prev supabase/studio:2025.11.24-sha-d990ae8)
- supabase/realtime:v2.65.3 (prev supabase/realtime:v2.65.2)
- supabase/logflare:1.26.13 (prev supabase/logflare:1.26.12)

## 2025-11-25
- supabase/studio:2025.11.24-sha-d990ae8 (prev supabase/studio:2025.11.10-sha-5291fe3)
- supabase/gotrue:v2.183.0 (prev supabase/gotrue:v2.182.1)
- supabase/realtime:v2.65.2 (prev supabase/realtime:v2.63.0)
- supabase/storage-api:v1.32.0 (prev supabase/storage-api:v1.29.0)
- supabase/edge-runtime:v1.69.25 (prev supabase/edge-runtime:v1.69.23)
- supabase/logflare:1.26.12 (prev supabase/logflare:1.22.6)

## 2025-11-12
- supabase/studio:2025.11.10-sha-5291fe3 (prev 2025.10.27-sha-85b84e0)
- supabase/gotrue:v2.182.1 (prev v2.180.0)
- supabase/realtime:v2.63.0 (prev v2.57.2)
- supabase/storage-api:v1.29.0 (prev v1.28.2)
- supabase/edge-runtime:v1.69.23 (prev v1.69.15)
- supabase/supavisor:2.7.4 (prev 2.7.3)

## 2025-10-28
- supabase/studio:2025.10.27-sha-85b84e0 (prev 2025.10.20-sha-5005fc6)
- supabase/realtime:v2.57.2 (prev v2.56.0)
- supabase/storage-api:v1.28.2 (prev v1.28.1)
- supabase/postgres-meta:v0.93.1 (prev v0.93.0)
- supabase/edge-runtime:v1.69.15 (prev v1.69.14)

## 2025-10-21
- supabase/studio:2025.10.20-sha-5005fc6 (prev 2025.10.01-sha-8460121)
- supabase/realtime:v2.56.0 (prev v2.51.11)
- supabase/storage-api:v1.28.1 (prev v1.28.0)
- supabase/postgres-meta:v0.93.0 (prev v0.91.6)
- supabase/edge-runtime:v1.69.14 (prev v1.69.6)
- supabase/supavisor:2.7.3 (prev 2.7.0)

## 2025-10-13
- supabase/logflare:1.22.6 (prev 1.22.4)

## 2025-10-08
- supabase/studio:2025.10.01-sha-8460121 (prev 2025.06.30-sha-6f5982d)
- supabase/gotrue:v2.180.0 (prev v2.177.0)
- postgrest/postgrest:v13.0.7 (prev v12.2.12)
- supabase/realtime:v2.51.11 (prev v2.34.47)
- supabase/storage-api:v1.28.0 (prev v1.25.7)
- supabase/postgres-meta:v0.91.6 (prev v0.91.0)
- supabase/logflare:1.22.4 (prev 1.14.2)
- supabase/postgres:15.8.1.085 (prev 15.8.1.060)
- supabase/supavisor:2.7.0 (prev 2.5.7)

## 2025-07-15
- supabase/gotrue:v2.177.0 (prev v2.176.1)
- supabase/storage-api:v1.25.7 (prev v1.24.7)
- supabase/postgres-meta:v0.91.0 (prev v0.89.3)
- supabase/supavisor:2.5.7 (prev 2.5.6)

## 2025-07-02
- supabase/studio:2025.06.30-sha-6f5982d (prev 2025.06.02-sha-8f2993d)
- supabase/gotrue:v2.176.1 (prev v2.174.0)
- supabase/storage-api:v1.24.7 (prev v1.23.0)
- supabase/supavisor:2.5.6 (prev 2.5.1)

## 2025-06-03
- supabase/studio:2025.06.02-sha-8f2993d (prev 2025.05.19-sha-3487831)
- supabase/gotrue:v2.174.0 (prev v2.172.1)
- supabase/storage-api:v1.23.0 (prev v1.22.17)
- supabase/postgres-meta:v0.89.3 (prev v0.89.0)


--- docker/volumes/functions/hello/index.ts ---
// Follow this setup guide to integrate the Deno language server with your editor:
// https://deno.land/manual/getting_started/setup_your_environment
// This enables autocomplete, go to definition, etc.

import { serve } from "https://deno.land/std@0.177.1/http/server.ts"

serve(async () => {
  return new Response(
    `"Hello from Edge Functions!"`,
    { headers: { "Content-Type": "application/json" } },
  )
})

// To invoke:
// curl 'http://localhost:<KONG_HTTP_PORT>/functions/v1/hello' \
//   --header 'Authorization: Bearer <anon/service_role API key>'


--- docker/volumes/functions/main/index.ts ---
import { serve } from 'https://deno.land/std@0.131.0/http/server.ts'
import * as jose from 'https://deno.land/x/jose@v4.14.4/index.ts'

console.log('main function started')

const JWT_SECRET = Deno.env.get('JWT_SECRET')
const VERIFY_JWT = Deno.env.get('VERIFY_JWT') === 'true'

function getAuthToken(req: Request) {
  const authHeader = req.headers.get('authorization')
  if (!authHeader) {
    throw new Error('Missing authorization header')
  }
  const [bearer, token] = authHeader.split(' ')
  if (bearer !== 'Bearer') {
    throw new Error(`Auth header is not 'Bearer {token}'`)
  }
  return token
}

async function verifyJWT(jwt: string): Promise<boolean> {
  const encoder = new TextEncoder()
  const secretKey = encoder.encode(JWT_SECRET)
  try {
    await jose.jwtVerify(jwt, secretKey)
  } catch (err) {
    console.error(err)
    return false
  }
  return true
}

serve(async (req: Request) => {
  if (req.method !== 'OPTIONS' && VERIFY_JWT) {
    try {
      const token = getAuthToken(req)
      const isValidJWT = await verifyJWT(token)

      if (!isValidJWT) {
        return new Response(JSON.stringify({ msg: 'Invalid JWT' }), {
          status: 401,
          headers: { 'Content-Type': 'application/json' },
        })
      }
    } catch (e) {
      console.error(e)
      return new Response(JSON.stringify({ msg: e.toString() }), {
        status: 401,
        headers: { 'Content-Type': 'application/json' },
      })
    }
  }

  const url = new URL(req.url)
  const { pathname } = url
  const path_parts = pathname.split('/')
  const service_name = path_parts[1]

  if (!service_name || service_name === '') {
    const error = { msg: 'missing function name in request' }
    return new Response(JSON.stringify(error), {
      status: 400,
      headers: { 'Content-Type': 'application/json' },
    })
  }

  const servicePath = `/home/deno/functions/${service_name}`
  console.error(`serving the request with ${servicePath}`)

  const memoryLimitMb = 150
  const workerTimeoutMs = 1 * 60 * 1000
  const noModuleCache = false
  const importMapPath = null
  const envVarsObj = Deno.env.toObject()
  const envVars = Object.keys(envVarsObj).map((k) => [k, envVarsObj[k]])

  try {
    const worker = await EdgeRuntime.userWorkers.create({
      servicePath,
      memoryLimitMb,
      workerTimeoutMs,
      noModuleCache,
      importMapPath,
      envVars,
    })
    return await worker.fetch(req)
  } catch (e) {
    const error = { msg: e.toString() }
    return new Response(JSON.stringify(error), {
      status: 500,
      headers: { 'Content-Type': 'application/json' },
    })
  }
})


--- e2e/studio/README.md ---
# Supabase Studio E2E Tests

## Set up

### Prerequisites

#### For Self-Hosted Tests

- Nothing is required, running with IS_PLATFORM=false should run the tests locally with a self hosted docker container

#### For Platform Tests

1. **Create a platform account** - You can authenticate using either:
   - Email and password
   - GitHub OAuth (requires TOTP 2FA)
2. **Create an organization** on the platform, this can be done if run locally through `mise fullstack`
3. **Generate a Personal Access Token (PAT)** for API access
4. Configure the environment variables below (see Authentication section for details on email vs GitHub auth)

### Configure Environment

Choose the appropriate example file based on your testing scenario:

**For self-hosted tests:**

```bash
cp .env.local.self-hosted.example .env.local
```

**For platform tests with email authentication:**

```bash
cp .env.local.email.example .env.local
```

**For platform tests with GitHub authentication:**

```bash
cp .env.local.github.example .env.local
```

Edit `.env.local` and set the appropriate values based on your test environment (see Environment Variables section below).

### Install the playwright browser

⚠️ This should be done in the `e2e/studio` directory

```bash
cd e2e/studio

pnpm exec playwright install
```

### Environment Variables

Configure your tests by setting the following environment variables in `.env.local`. We have examples of what required on self hosted and platform:

#### Core Configuration

- **`STUDIO_URL`**: The URL where Studio is running (default: `http://localhost:8082`)
- **`API_URL`**: The Supabase API endpoint (default: `https://localhost:8080`)
- **`IS_PLATFORM`**: Set to `true` for platform tests, `false` for self-hosted (default: `false`)
  - When `true`: Tests run serially (1 worker) due to API rate limits
  - When `false`: Tests run in parallel (5 workers)

#### Authentication (Required for Platform Tests)

⚠️ **Before running platform tests, you must create an account and organization on the platform you're testing.**

Authentication is automatically enabled when either email/password OR GitHub credentials are configured.

##### Email Authentication

- **`EMAIL`**: Your platform account email
- **`PASSWORD`**: Your platform account password
- **`PROJECT_REF`**: Project reference (optional, will be auto-created if not provided)

When both `EMAIL` and `PASSWORD` are set, the tests will authenticate using email/password. HCaptcha is mocked during test setup. Note this only works on local and staging environments

##### GitHub Authentication

- **`GITHUB_USER`**: Your GitHub username
- **`GITHUB_PASS`**: Your GitHub password
- **`GITHUB_TOTP`**: Your GitHub TOTP secret for 2FA (required, as GitHub enforces 2FA)

When `GITHUB_USER`, `GITHUB_PASS`, and `GITHUB_TOTP` are all set, the tests will authenticate using GitHub OAuth with TOTP-based 2FA. The authentication flow handles:

- Clicking "Sign In with GitHub" button
- Filling GitHub credentials
- Generating and submitting TOTP codes
- Handling GitHub authorization prompts
- Automatic retry on failure (up to 3 attempts)

**Getting your GitHub TOTP secret:**
When setting up 2FA on GitHub, you'll see a QR code. Click "enter this text code instead" to reveal the secret key. This is the value to use for `GITHUB_TOTP`.

#### Platform-Specific Variables (Required when `IS_PLATFORM=true`)

- **`ORG_SLUG`**: Organization slug (default: `default`)
- **`SUPA_REGION`**: Supabase region (default: `us-east-1`)
- **`SUPA_PAT`**: Personal Access Token for API authentication (default: `test`)
- **`BRANCH_NAME`**: Name for the test branch/project (default: `e2e-test-local`)

#### Optional Variables

- **`OPENAI_API_KEY`**: Required for the AI Assistant test (`assistant.spec.ts`). Without this variable, the assistant test will be skipped.
- **`VERCEL_AUTOMATION_BYPASS_SELFHOSTED_STUDIO`**: Bypass token for Vercel protection (default: `false`)

#### Setup Commands Based on Configuration

The test setup automatically runs different commands based on your environment:

- **Platform + Localhost** (`IS_PLATFORM=true` and `STUDIO_URL=localhost`): Runs `pnpm run e2e:setup:platform`
- **Platform + Remote** (`IS_PLATFORM=true` and remote `STUDIO_URL`): No web server setup
- **Self-hosted** (`IS_PLATFORM=false`): Runs `pnpm run e2e:setup:selfhosted`

---

## Running the tests

Check the `package.json` for the available commands and environments.

```bash
pnpm run e2e
```

With Playwright UI:

```bash
pnpm run e2e -- --ui
```

---

## Tips for development

- Read [Playwright Best Practices](https://playwright.dev/docs/best-practices)
- Use `pnpm run e2e -- --ui` to get the playwright UI.
- Add the tests in `examples/examples.ts` to Cursor as context.
- Add messages to expect statements to make them easier to debug.

Example:

```ts
await expect(page.getByRole('heading', { name: 'Logs & Analytics' }), {
  message: 'Logs heading should be visible',
}).toBeVisible()
```

- Use the test utility instead of playwrights test.

```ts
import { test } from '../utils/test'
```

- Use the PWDEBUG environment variable to debug the tests.

```bash
PWDEBUG=1 pnpm run e2e -- --ui
```

---

## What should I test?

- Can the feature be navigated to?
- Does the feature load correctly?
- Can you do the actions (filtering, sorting, opening dialogs, etc)?

---

## API Mocks

Read here: https://playwright.dev/docs/mock#mock-api-requests

Example:

```ts
await page.route(`*/**/logs.all*`, async (route) => {
  await route.fulfill({ body: JSON.stringify(mockAPILogs) })
})
```


## Links discovered
- [Playwright Best Practices](https://playwright.dev/docs/best-practices)

--- e2e/studio/env.config.ts ---
import dotenv from 'dotenv'
import path from 'path'

// Load .env.local before reading process.env
dotenv.config({
  path: path.resolve(import.meta.dirname, '.env.local'),
  override: true,
})

const toBoolean = (value?: string) => {
  if (value == null) return false
  const normalized = value.trim().toLowerCase()
  return normalized === 'true'
}

export const env = {
  STUDIO_URL: process.env.STUDIO_URL || 'http://localhost:8082',
  API_URL: process.env.API_URL || 'https://localhost:8080',

  IS_PLATFORM: toBoolean(process.env.IS_PLATFORM || 'false'),
  EMAIL: process.env.EMAIL,
  PASSWORD: process.env.PASSWORD,
  PROJECT_REF: process.env.PROJECT_REF || undefined,

  GITHUB_USER: process.env.GITHUB_USER,
  GITHUB_PASS: process.env.GITHUB_PASS,
  GITHUB_TOTP: process.env.GITHUB_TOTP,

  VERCEL_AUTOMATION_BYPASS_SELFHOSTED_STUDIO:
    process.env.VERCEL_AUTOMATION_BYPASS_SELFHOSTED_STUDIO || 'false',
  ORG_SLUG: process.env.ORG_SLUG || 'default',
  SUPA_REGION: process.env.SUPA_REGION || 'us-east-1',
  SUPA_PAT: process.env.SUPA_PAT || 'test',

  BRANCH_NAME: process.env.BRANCH_NAME || `e2e-test-local`,

  AUTHENTICATION:
    Boolean(process.env.EMAIL && process.env.PASSWORD) ||
    Boolean(process.env.GITHUB_USER && process.env.GITHUB_PASS && process.env.GITHUB_TOTP),

  IS_APP_RUNNING_ON_LOCALHOST:
    process.env.STUDIO_URL?.includes('localhost') || process.env.STUDIO_URL?.includes('127.0.0.1'),
}

export const STORAGE_STATE_PATH = path.join(import.meta.dirname, './playwright/.auth/user.json')


--- e2e/studio/playwright.config.ts ---
import { defineConfig } from '@playwright/test'
import { env, STORAGE_STATE_PATH } from './env.config.js'

const IS_CI = !!process.env.CI

const WEB_SERVER_TIMEOUT = Number(process.env.WEB_SERVER_TIMEOUT) || 10 * 60 * 1000
const WEB_SERVER_PORT = Number(process.env.WEB_SERVER_PORT) || 8082

// 15 minutes for platform, 2 minutes for self-hosted. Takes longer to setup a full project on platform.
const setupTimeout = env.IS_PLATFORM ? 15 * 60 * 1000 : 120 * 1000

const createWebServerConfig = () => {
  if (env.IS_PLATFORM && env.IS_APP_RUNNING_ON_LOCALHOST) {
    return {
      command: 'pnpm --workspace-root run e2e:setup:platform',
      port: WEB_SERVER_PORT,
      timeout: WEB_SERVER_TIMEOUT,
      reuseExistingServer: true,
    }
  }

  // Apps running on runner using the vercel staging environment
  if (env.IS_PLATFORM && !env.IS_APP_RUNNING_ON_LOCALHOST) {
    return undefined
  }

  return {
    command: 'pnpm --workspace-root run e2e:setup:selfhosted',
    port: WEB_SERVER_PORT,
    timeout: WEB_SERVER_TIMEOUT,
    reuseExistingServer: true,
  }
}

export default defineConfig({
  timeout: 120 * 1000,
  testDir: './features',
  testMatch: /.*\.spec\.ts/,
  forbidOnly: IS_CI,
  retries: IS_CI ? 5 : 0,
  maxFailures: 3,
  expect: {
    timeout: 20_000,
  },
  // Due to rate API rate limits run tests in serial mode on platform.
  fullyParallel: !env.IS_PLATFORM,
  workers: env.IS_PLATFORM ? 1 : 3,
  use: {
    baseURL: env.STUDIO_URL,
    screenshot: 'off',
    video: 'retain-on-failure',
    headless: true || IS_CI,
    trace: 'retain-on-failure',
    permissions: ['clipboard-read', 'clipboard-write'],
    extraHTTPHeaders: {
      'x-vercel-protection-bypass':
        process.env.VERCEL_AUTOMATION_BYPASS_SELFHOSTED_STUDIO || 'false',
      'x-vercel-set-bypass-cookie': 'true',
    },
    launchOptions: {
      args: [
        // Security/Sandbox settings (required for CI environments)
        '--no-sandbox', // Disables Chrome's sandbox - required in Docker/CI where user namespaces aren't available
        '--disable-setuid-sandbox', // Alternative sandbox method - disabled for CI compatibility
        '--allow-insecure-localhost', // Allows tests against localhost with self-signed certificates
        // Memory and resource management
        '--disable-dev-shm-usage', // Use /tmp instead of /dev/shm to avoid shared memory issues in containers
        '--js-flags=--max_old_space_size=4096', // Increase V8 heap size to 4GB to handle memory-intensive tests
        '--memory-pressure-off', // Prevents Chrome from killing tabs due to memory pressure in CI
        '--enable-low-end-device-mode', // Optimizes memory usage for resource-constrained environments
        // GPU and rendering (disabled for headless/CI performance)
        '--disable-gpu', // Disables hardware GPU - not needed in headless mode
        '--disable-software-rasterizer', // Disables software-based rendering fallback
        // Performance optimizations for testing
        '--disable-background-timer-throttling', // Prevents Chrome from throttling timers in background tabs
        '--disable-backgrounding-occluded-windows', // Keeps hidden windows running at full speed
        '--disable-renderer-backgrounding', // Prevents renderer processes from being deprioritized
        '--disable-ipc-flooding-protection', // Allows high-frequency IPC messages needed for automation
        // Disable unnecessary features to reduce overhead
        '--disable-extensions', // Disables all browser extensions
        '--disable-sync', // Disables Chrome sync service
        '--disable-default-apps', // Prevents loading of default Chrome apps
        '--disable-component-update', // Disables automatic component updates during tests
        '--disable-background-networking', // Disables background network requests
        '--disable-features=TranslateUI', // Disables translation UI prompts
        '--disable-features=MediaRouter,site-per-process', // Disables Cast and site isolation for performance
        '--disable-features=HardwareMediaKeyHandling', // Disables hardware media key handling
        // Disable monitoring and crash reporting
        '--disable-breakpad', // Disables crash reporting system
        '--disable-crash-reporter', // Disables crash reporter UI
        '--disable-hang-monitor', // Disables hang detection monitoring
        '--metrics-recording-only', // Disables metric uploads while still collecting them
        // Disable security features not needed for testing
        '--disable-client-side-phishing-detection', // Disables phishing detection checks
        '--safebrowsing-disable-auto-update', // Disables safe browsing database updates
        '--disable-domain-reliability', // Disables domain reliability monitoring
        // Disable user prompts and UI elements
        '--disable-popup-blocking', // Allows popups without user confirmation
        '--disable-prompt-on-repost', // Skips form resubmission confirmation dialogs
        '--no-first-run', // Skips first-run wizards and setup dialogs
        '--no-default-browser-check', // Prevents "set as default browser" prompts
        // Process management
        '--no-zygote', // Disables zygote process for spawning renderers - reduces memory in single-use scenarios
        // Headless mode configuration
        '--headless=new', // Uses new headless mode (more stable than old headless)
        '--window-size=1280,720', // Sets consistent viewport size for screenshot/visual consistency
        '--hide-scrollbars', // Hides scrollbars for cleaner screenshots
        '--mute-audio', // Prevents audio output during tests
        // Network configuration
        '--enable-features=NetworkService,NetworkServiceInProcess', // Uses modern network service in-process for better performance
      ],
    },
  },
  projects: [
    {
      name: 'setup',
      testMatch: /.*\.setup\.ts/,
      timeout: setupTimeout,
    },
    {
      name: 'Features',
      testDir: './features',
      testMatch: /.*\.spec\.ts/,
      dependencies: ['setup'],
      use: {
        browserName: 'chromium',
        screenshot: 'off',

        // Only use storage state if authentication is enabled. When AUTHENTICATION=false
        // we should not require a pre-generated storage state file.
        storageState: env.AUTHENTICATION ? STORAGE_STATE_PATH : undefined,
      },
    },
  ],
  reporter: [
    ['list'],
    ['html', { open: 'never' }],
    ['json', { outputFile: 'test-results/test-results.json' }],
  ],
  webServer: createWebServerConfig(),
})


--- e2e/studio/features/assistant.spec.ts ---
import { expect } from '@playwright/test'
import { test } from '../utils/test.js'
import { toUrl } from '../utils/to-url.js'

test.describe('AI Assistant', async () => {
  test('Can send a message to the assistant and receive a response', async ({ page, ref }) => {
    // Skip the test if the OPENAI_API_KEY is not set
    test.skip(!process.env.OPENAI_API_KEY, 'OPENAI_API_KEY is not set')

    await page.goto(toUrl(`/project/${ref}`))

    // Wait for the page to load
    await expect(page.getByRole('heading', { level: 1 })).toBeVisible()

    // Click the assistant button to open the assistant panel
    await page.locator('#assistant-trigger').click()

    // Wait for the assistant panel to be visible
    await expect(page.getByRole('heading', { name: 'How can I assist you?' })).toBeVisible()

    // Type "hello" in the chat input
    const chatInput = page.getByRole('textbox', { name: 'Chat to Postgres...' })
    await chatInput.fill('hello')

    const responsePromise = page.waitForResponse(
      (response) =>
        response.url().includes('/api/ai/sql/generate-v4') &&
        response.request().method() === 'POST',
      { timeout: 60000 }
    )

    // Click the send message button
    const sendButton = page.getByRole('button', { name: 'Send message' })
    await sendButton.click()

    // Wait for the API request to complete
    const response = await responsePromise

    // Verify the response was successful
    expect(response.status()).toBe(200)

    // AI response has values
    const body = await response.text()
    expect(body).toContain('data')
  })
})


--- e2e/studio/utils/auth-helpers.ts ---
import { expect, Page } from '@playwright/test'
import { waitForApiResponse } from './wait-for-response.js'
import { toUrl } from './to-url.js'

export const createUserViaUI = async (page: Page, ref: string, email: string, password: string) => {
  // Open the Add user dropdown
  await page.getByRole('button', { name: 'Add user' }).click()

  // Click "Create new user"
  await page.getByRole('menuitem', { name: 'Create new user' }).click()

  // Wait for dialog to be visible
  await expect(page.getByRole('dialog', { name: 'Create a new user' })).toBeVisible()

  // Fill in email
  await page.getByRole('textbox', { name: 'user@example.com' }).fill(email)

  // Fill in password
  await page.getByRole('textbox', { name: '••••••••' }).fill(password)

  // Verify that "Auto Confirm User?" is checked by default
  await expect(page.getByRole('checkbox', { name: 'Auto Confirm User?' })).toBeChecked()

  // Set up API waiters BEFORE clicking the button to avoid race conditions
  const createUserPromise = waitForApiResponse(page, 'platform/auth', ref, 'users', {
    method: 'POST',
  })
  const usersListPromise = waitForApiResponse(page, 'platform/pg-meta', ref, 'query?key=')

  // Click "Create user"
  await page.getByRole('button', { name: 'Create user' }).click()

  // Wait for both API calls to complete
  await Promise.all([createUserPromise, usersListPromise])

  // Verify the user appears in the table
  const userRow = page.getByRole('row').filter({ hasText: email })
  await expect(
    userRow,
    `User row with email ${email} should be visible after creation`
  ).toBeVisible({ timeout: 10_000 })
}

export const deleteUserViaUI = async (page: Page, ref: string, email: string) => {
  // Find the user row by email and click the checkbox
  const userRow = page.getByRole('row').filter({ hasText: email })
  await expect(userRow, `User row with email ${email} should be visible`).toBeVisible()

  // Click the checkbox to select the user
  await userRow.getByRole('checkbox').first().click()

  // Click "Delete 1 users" button
  await page.getByRole('button', { name: 'Delete 1 users' }).click()

  // Wait for confirmation dialog
  await expect(page.getByRole('dialog', { name: 'Confirm to delete 1 user' })).toBeVisible()

  // Set up API waiters BEFORE clicking the delete button
  const deleteUserPromise = waitForApiResponse(page, 'platform/auth', ref, 'users/', {
    method: 'DELETE',
  })
  const usersListPromise = waitForApiResponse(page, 'platform/pg-meta', ref, 'query?key=')

  // Confirm deletion
  await page.getByRole('button', { name: 'Delete' }).click()

  // Wait for both API calls to complete
  await Promise.all([deleteUserPromise, usersListPromise])

  // Verify the user is removed from the table
  await expect
    .poll(
      async () => {
        return await userRow.count()
      },
      {
        message: `User row with email ${email} should be removed after deletion`,
        timeout: 10_000,
      }
    )
    .toBe(0)
}

export const navigateToAuthUsers = async (page: Page, ref: string) => {
  const userListResponse = waitForApiResponse(page, 'platform/pg-meta', ref, 'query?key=')
  await page.goto(toUrl(`/project/${ref}/auth/users`))

  // Wait for the page to load by checking for the "Users" heading
  await expect(page.getByRole('heading', { name: 'Users', level: 3 })).toBeVisible()

  // Wait for initial users list to load
  await userListResponse
}


--- e2e/studio/features/auth-users.spec.ts ---
import { expect, Page } from '@playwright/test'
import { test } from '../utils/test.js'
import { toUrl } from '../utils/to-url.js'
import { waitForApiResponse } from '../utils/wait-for-response.js'
import { createUserViaUI, deleteUserViaUI, navigateToAuthUsers } from '../utils/auth-helpers.js'

test.describe('auth users list refresh', () => {
  test.beforeEach(async ({ page, ref }) => {
    await navigateToAuthUsers(page, ref)
  })

  test('should automatically refresh users list after creating a user', async ({ page, ref }) => {
    const testEmail = `test-create-${Date.now()}@example.com`
    const testPassword = 'testpassword123'

    // Create user via UI - this verifies the user appears in the table
    await createUserViaUI(page, ref, testEmail, testPassword)

    // Verify the user details are correct
    const userRow = page.getByRole('row').filter({ hasText: testEmail })
    await expect(userRow.getByText(testEmail)).toBeVisible()
    await expect(userRow.getByText('Email')).toBeVisible()

    // Clean up: delete the user - this verifies the user is removed from the table
    await deleteUserViaUI(page, ref, testEmail)
  })

  test('should automatically refresh users list after creating multiple users', async ({
    page,
    ref,
  }) => {
    const testUsers = [
      { email: `test-multi-1-${Date.now()}@example.com`, password: 'testpassword123' },
      { email: `test-multi-2-${Date.now()}@example.com`, password: 'testpassword123' },
      { email: `test-multi-3-${Date.now()}@example.com`, password: 'testpassword123' },
    ]

    // Create multiple users - each creation verifies the user appears in the table
    for (const user of testUsers) {
      await createUserViaUI(page, ref, user.email, user.password)
    }

    // Clean up: delete all test users - each deletion verifies the user is removed
    for (const user of testUsers) {
      await deleteUserViaUI(page, ref, user.email)
    }
  })
})


--- e2e/studio/features/database.spec.ts ---
import { expect, Page } from '@playwright/test'
import { env } from '../env.config.js'
import { test } from '../utils/test.js'
import { toUrl } from '../utils/to-url.js'
import {
  createApiResponseWaiter,
  waitForApiResponse,
  waitForDatabaseToLoad,
} from '../utils/wait-for-response.js'

const databaseTableName = 'pw_database_table'
const databaseTableNameNew = 'pw_database_table_new'
const databaseTableNameUpdated = 'pw_database_table_updated'
const databaseTableNameDuplicate = 'pw_database_table_duplicate'
const databaseColumnName = 'pw_database_column'
const databaseColumnName2 = 'pw_database_column_2'
const databaseColumnName3 = 'pw_database_column_3'
const databaseIndexName = 'pw_database_index'
const databaseEnumName = 'pw_database_enum'
const databaseEnumValue1Name = 'pw_database_value1'
const databaseEnumValue2Name = 'pw_database_value2'
const databaseEnumValue3Name = 'pw_database_value3'
const databaseTriggerName = 'pw_database_trigger'
const databaseTriggerNameUpdated = 'pw_database_trigger_updated'
const databaseFunctionName = 'pw_database_function'
const databaseFunctionNameUpdated = 'pw_database_function_updated'
const databaseRoleName = 'pw_database_role'

const createTable = async (page: Page, tableName: string, newColumnName: string) => {
  await page.getByRole('button', { name: 'New table', exact: true }).click()
  await page.getByTestId('table-name-input').fill(tableName)
  await page.getByTestId('created_at-extra-options').click()
  await page.getByText('Is Nullable').click()
  await page.getByTestId('created_at-extra-options').click({ force: true })

  await page.getByRole('button', { name: 'Add column' }).click()
  await page.getByRole('textbox', { name: 'column_name' }).fill(newColumnName)
  await page.getByText('Choose a column type...').click()
  await page.getByRole('option', { name: 'text Variable-length' }).click()

  await page.getByRole('button', { name: 'Save' }).click()

  await expect(
    page.getByText(`Table ${tableName} is good to go!`),
    'Success toast should be visible after table creation'
  ).toBeVisible({
    timeout: 50000,
  })

  await expect(
    page.getByRole('button', { name: `View ${tableName}`, exact: true }),
    'Table should be visible after creation'
  ).toBeVisible()
}

const deleteTable = async (page: Page, tableName: string) => {
  await page.getByLabel(`View ${tableName}`, { exact: true }).nth(0).click()
  await page.getByLabel(`View ${tableName}`, { exact: true }).getByRole('button').nth(1).click()
  await page.getByText('Delete table').click()
  await page.getByRole('checkbox', { name: 'Drop table with cascade?' }).click()
  await page.getByRole('button', { name: 'Delete' }).click()
  await expect(
    page.getByText(`Successfully deleted table "${tableName}"`),
    'Delete confirmation toast should be visible'
  ).toBeVisible({ timeout: 50000 })
}

test.describe.serial('Database', () => {
  let page: Page

  test.beforeAll(async ({ browser, ref }) => {
    page = await browser.newPage()
    const wait = createApiResponseWaiter(page, 'pg-meta', ref, 'query?key=entity-types-public-0')
    await page.goto(toUrl(`/project/${ref}/editor`))
    await wait

    if ((await page.getByRole('button', { name: `View ${databaseTableName}` }).count()) > 0) {
      await deleteTable(page, databaseTableName)
    }

    await createTable(page, databaseTableName, databaseColumnName)
  })

  test.afterAll(async ({ ref }) => {
    const wait = createApiResponseWaiter(page, 'pg-meta', ref, 'query?key=entity-types-public-0')
    await page.goto(toUrl(`/project/${ref}/editor`))
    await wait
    if ((await page.getByRole('button', { name: `View ${databaseTableName}` }).count()) > 0) {
      await deleteTable(page, databaseTableName)
    }
  })

  test.describe('Schema Visualizer', () => {
    test('actions works as expected', async ({ page, ref }) => {
      const wait = createApiResponseWaiter(
        page,
        'pg-meta',
        ref,
        'tables?include_columns=true&included_schemas=public'
      )
      await page.goto(toUrl(`/project/${env.PROJECT_REF}/database/schemas?schema=public`))
      await wait

      // validates table and column exists
      await page.waitForTimeout(500)
      await expect(page.getByText(databaseTableName, { exact: true })).toBeVisible()
      await expect(page.getByText(databaseColumnName)).toBeVisible()

      // copies schema definition to clipboard
      await page.getByRole('button', { name: 'Copy as SQL' }).click()
      await page.waitForTimeout(500)
      const clipboardText = await page.evaluate(() => navigator.clipboard.readText())
      expect(clipboardText).toContain(`CREATE TABLE public.pw_database_table (
  id bigint GENERATED ALWAYS AS IDENTITY NOT NULL,
  created_at timestamp with time zone DEFAULT now(),
  pw_database_column text,
  CONSTRAINT pw_database_table_pkey PRIMARY KEY (id)
);`)

      // downloads schema diagram when export is triggered
      const downloadPromise = page.waitForEvent('download')
      await page.getByRole('button', { name: 'Download Schema' }).click()
      await page.getByRole('menuitem', { name: 'Download as PNG' }).click()
      const download = await downloadPromise
      await expect(download.suggestedFilename()).toContain('.png')

      // changing schema -> auth
      await page.getByTestId('schema-selector').click()
      await page.getByRole('option', { name: 'auth' }).click()
      await waitForDatabaseToLoad(page, ref, 'auth')
      await expect(page.getByText('users')).toBeVisible()
      await expect(page.getByText('sso_providers')).toBeVisible()
      await expect(page.getByText('saml_providers')).toBeVisible()

      // navigate to table editor when icon is clicked
      const samlProvidersHeader = await page.getByText('saml_providers')
      await samlProvidersHeader.locator('..').getByRole('link').click()
      await page.waitForURL(/.*\/editor\/\d+/)
      await page.getByRole('button', { name: 'View saml_providers', exact: true }).click()
    })
  })

  test.describe.serial('Tables', () => {
    test('actions works as expected', async ({ page, ref }) => {
      const wait = createApiResponseWaiter(
        page,
        'pg-meta',
        ref,
        'tables?include_columns=true&included_schemas=public'
      )
      await page.goto(toUrl(`/project/${env.PROJECT_REF}/database/tables?schema=public`))
      await wait

      // check new table button is present in public schema
      await expect(page.getByRole('button', { name: 'New table' })).toBeVisible()

      // validates database name is present and has accurate number of columns
      const tableRow = await page.getByRole('row', {
        name: `${databaseTableName} No description`,
      })
      await expect(tableRow).toContainText(databaseTableName)
      await expect(tableRow).toContainText('3 columns')

      // change schema -> auth
      await page.getByTestId('schema-selector').click()
      await page.getByPlaceholder('Find schema...').fill('auth')
      const authSchemaWait = createApiResponseWaiter(
        page,
        'pg-meta',
        ref,
        'tables?include_columns=true&included_schemas=auth'
      )
      await page.getByRole('option', { name: 'auth' }).click()
      await authSchemaWait
      await expect(page.getByText('sso_providers')).toBeVisible()
      // check new table button is not present in other schemas
      await expect(page.getByRole('button', { name: 'New table' })).not.toBeVisible()

      // filter by querying
      await page.getByRole('textbox', { name: 'Search for a table' }).fill('mfa')
      await page.waitForTimeout(500)
      await expect(page.getByText('sso_providers')).not.toBeVisible()
      await expect(page.getByText('mfa_factors')).toBeVisible()
    })

    test('CRUD operations and copy works as expected', async ({ page, ref }) => {
      await page.goto(toUrl(`/project/${env.PROJECT_REF}/database/tables?schema=public`))

      // Wait for database tables to be populated
      await waitForDatabaseToLoad(page, ref)

      // drop database tables if exists
      if ((await page.getByText(databaseTableNameNew, { exact: true }).count()) > 0) {
        await page
          .getByRole('row', { name: databaseTableNameNew })
          .getByRole('button')
          .last()
          .click()
        await page.getByRole('menuitem', { name: 'Delete table' }).click()
        await page.getByRole('checkbox', { name: 'Drop table with cascade?' }).check()
        const cleanupNewWait = createApiResponseWaiter(
          page,
          'pg-meta',
          ref,
          'query?key=table-delete'
        )
        await page.getByRole('button', { name: 'Delete' }).click()
        await cleanupNewWait
      }

      if ((await page.getByText(databaseTableNameUpdated, { exact: true }).count()) > 0) {
        await page
          .getByRole('row', { name: databaseTableNameUpdated })
          .getByRole('button')
          .last()
          .click()
        await page.getByRole('menuitem', { name: 'Delete table' }).click()
        await page.getByRole('checkbox', { name: 'Drop table with cascade?' }).check()
        const cleanupUpdatedWait = createApiResponseWaiter(
          page,
          'pg-meta',
          ref,
          'query?key=table-delete'
        )
        await page.getByRole('button', { name: 'Delete' }).click()
        await cleanupUpdatedWait
      }

      if ((await page.getByText(databaseTableNameDuplicate, { exact: true }).count()) > 0) {
        await page
          .getByRole('row', { name: databaseTableNameDuplicate })
          .getByRole('button')
          .last()
          .click()
        await page.getByRole('menuitem', { name: 'Delete table' }).click()
        await page.getByRole('checkbox', { name: 'Drop table with cascade?' }).check()
        const cleanupDuplicateWait = createApiResponseWaiter(
          page,
          'pg-meta',
          ref,
          'query?key=table-delete'
        )
        await page.getByRole('button', { name: 'Delete' }).click()
        await cleanupDuplicateWait
      }

      // create a new table
      await page.getByRole('button', { name: 'New table' }).click()
      await page.getByTestId('table-name-input').fill(databaseTableNameNew)
      const createTableWait = createApiResponseWaiter(
        page,
        'pg-meta',
        ref,
        'query?key=table-create'
      )
      await page.getByRole('button', { name: 'Save' }).click()

      // validate table creation
      await createTableWait
      await waitForDatabaseToLoad(page, ref)
      await expect(page.getByText(databaseTableNameNew, { exact: true })).toBeVisible()

      // edit a new table
      await page.getByRole('row', { name: databaseTableNameNew }).getByRole('button').last().click()
      await page.getByRole('menuitem', { name: 'Edit table' }).click()
      await page.getByTestId('table-name-input').fill(databaseTableNameUpdated)
      const updateTableWait = createApiResponseWaiter(
        page,
        'pg-meta',
        ref,
        'query?key=table-update'
      )
      await page.getByRole('button', { name: 'Save' }).click()

      // validate table update
      await updateTableWait
      await waitForDatabaseToLoad(page, ref)
      await expect(page.getByText(databaseTableNameUpdated, { exact: true })).toBeVisible()

      // duplicate table
      await page
        .getByRole('row', { name: databaseTableNameUpdated })
        .getByRole('button')
        .last()
        .click()
      await page.getByRole('menuitem', { name: 'Duplicate Table' }).click()
      await page.getByTestId('table-name-input').fill(databaseTableNameDuplicate)
      await page.getByRole('textbox', { name: 'Optional' }).fill('')
      const duplicateTableWait = createApiResponseWaiter(page, 'pg-meta', ref, 'query?key=')
      await page.getByRole('button', { name: 'Save' }).click()

      // validate table duplicate
      await duplicateTableWait
      await waitForDatabaseToLoad(page, ref)
      await expect(page.getByText(databaseTableNameDuplicate, { exact: true })).toBeVisible()

      // delete tables
      await page
        .getByRole('row', { name: `${databaseTableNameDuplicate}` })
        .getByRole('button')
        .last()
        .click()
      await page.getByRole('menuitem', { name: 'Delete table' }).click()
      await page.getByRole('checkbox', { name: 'Drop table with cascade?' }).check()
      const deleteDuplicateWait = createApiResponseWaiter(
        page,
        'pg-meta',
        ref,
        'query?key=table-delete'
      )
      await page.getByRole('button', { name: 'Delete' }).click()
      await deleteDuplicateWait

      await page
        .getByRole('row', { name: `${databaseTableNameUpdated}` })
        .getByRole('button')
        .last()
        .click()
      await page.getByRole('menuitem', { name: 'Delete table' }).click()
      await page.getByRole('checkbox', { name: 'Drop table with cascade?' }).check()
      const deleteUpdatedWait = createApiResponseWaiter(
        page,
        'pg-meta',
        ref,
        'query?key=table-delete'
      )
      await page.getByRole('button', { name: 'Delete' }).click()
      await deleteUpdatedWait

      // validate navigating to table editor from database table page
      await page.getByRole('row', { name: databaseTableName }).getByRole('button').last().click()
      await page.getByRole('menuitem', { name: 'View in Table Editor' }).click()
      await page.waitForTimeout(1000) // wait for the table editor to be loaded
      expect(page.url().includes('editor')).toBe(true)
    })
  })

  test.describe('Tables columns', () => {
    test('can view, create, update, delete, and filter table columns', async ({ page, ref }) => {
      await page.goto(toUrl(`/project/${env.PROJECT_REF}/database/tables?schema=public`))

      // Wait for database tables to be populated
      await waitForDatabaseToLoad(page, ref)

      // navigate to table columns
      const databaseRow = page.getByRole('row', { name: databaseTableName })
      await databaseRow.getByRole('link', { name: '3 columns' }).click()
      await page.waitForURL(/.*\/database\/tables\/\d+/)

      // validate and display everything correctly
      const columnDatabaseRow = page.getByRole('row', { name: databaseColumnName })
      await expect(columnDatabaseRow).toContainText(databaseColumnName)
      await expect(columnDatabaseRow).toContainText('text')

      // create a new table column
      await page.getByRole('button', { name: 'New column' }).click()
      await page
        .getByRole('textbox', { name: 'column_name', exact: true })
        .fill('pw_database_column_2')
      await page.getByText('Choose a column type...').click()
      await page.getByText('numeric', { exact: true }).click()
      const columnCreateWait = createApiResponseWaiter(
        page,
        'pg-meta',
        ref,
        'query?key=column-create'
      )
      const columnCreateRefreshWait = createApiResponseWaiter(
        page,
        'pg-meta',
        ref,
        'query?key=table-editor-'
      )
      await page.getByRole('button', { name: 'Save' }).click()

      // wait for response + validate
      await columnCreateWait
      await columnCreateRefreshWait
      const columnDatabase2Row = page.getByRole('row', { name: databaseColumnName2 })
      await expect(columnDatabase2Row).toContainText(databaseColumnName2)
      await expect(columnDatabase2Row).toContainText('numeric')

      // update table column
      await columnDatabase2Row.getByRole('button').click()
      await page.getByRole('button', { name: 'Edit column' }).click()
      await page.getByRole('textbox', { name: 'column_name' }).fill(databaseColumnName3)
      const columnUpdateWait = createApiResponseWaiter(
        page,
        'pg-meta',
        ref,
        'query?key=column-update'
      )
      const columnUpdateRefreshWait = createApiResponseWaiter(
        page,
        'pg-meta',
        ref,
        'query?key=table-editor-'
      )
      await page.getByRole('button', { name: 'Save' }).click()

      // wait for response + validate
      await columnUpdateWait
      await columnUpdateRefreshWait

      // delete table column
      const columnDatabase3Row = page.getByRole('row', { name: databaseColumnName3 })
      await columnDatabase3Row.getByRole('button').click()
      await page.getByRole('button', { name: 'Delete column' }).click()
      await page.getByRole('checkbox', { name: 'Drop column with cascade?' }).check()
      const columnDeleteWait = createApiResponseWaiter(
        page,
        'pg-meta',
        ref,
        'query?key=column-delete'
      )
      const columnDeleteRefreshWait = createApiResponseWaiter(
        page,
        'pg-meta',
        ref,
        'query?key=table-editor-'
      )
      await page.getByRole('button', { name: 'Delete' }).click()

      // wait for response + validate
      await columnDeleteWait
      await columnDeleteRefreshWait
      await expect(
        page.getByText(`Successfully deleted column "${databaseColumnName3}"`),
        'Delete confirmation toast should be visible'
      ).toBeVisible()

      // test filtering columns
      await page.getByRole('textbox', { name: 'Filter columns' }).fill('id')
      await expect(page.getByRole('row', { name: 'id' })).toBeVisible()
      await expect(page.getByRole('row', { name: databaseColumnName })).not.toBeVisible()
    })
  })

  test.describe.serial('Triggers', () => {
    test('actions works as expected', async ({ page, ref }) => {
      await page.goto(toUrl(`/project/${env.PROJECT_REF}/database/triggers?schema=public`))

      // Wait for database triggers to be populated
      await waitForApiResponse(page, 'pg-meta', ref, 'triggers')

      const newTriggerButton = page.getByRole('button', { name: 'New trigger' }).first()
      // create new trigger button to exist in public schema
      await expect(newTriggerButton).toBeVisible()

      // change schema -> realtime
      await page.getByTestId('schema-selector').click()
      await page.getByPlaceholder('Find schema...').fill('realtime')
      await page.getByRole('option', { name: 'realtime', exact: true }).click()
      await expect(page.getByText('tr_check_filters')).toBeVisible()
      // create new trigger button does not exist in other schemas
      await expect(page.getByRole('button', { name: 'New trigger' })).not.toBeVisible()

      // filter by querying
      await page.getByRole('textbox', { name: 'Search for a trigger' }).fill('abc')
      await page.waitForTimeout(500) // wait for enum types to be loaded
      await expect(page.getByText('tr_check_filters')).not.toBeVisible()
    })

    test('CRUD operations works as expected', async ({ page, ref }) => {
      await page.goto(toUrl(`/project/${env.PROJECT_REF}/database/triggers?schema=public`))

      // Wait for database triggers to be populated
      await waitForApiResponse(page, 'pg-meta', ref, 'triggers')

      // delete trigger if exists
      if ((await page.getByRole('button', { name: databaseTriggerName }).count()) > 0) {
        const triggerRow = await page.getByRole('row', { name: databaseTriggerName })
        await triggerRow.getByRole('button', { name: 'More options' }).click()
        await page.getByRole('menuitem', { name: 'Delete trigger' }).click()
        await page.getByPlaceholder('Type in name of trigger').fill(databaseTriggerName)
        await page.getByRole('button', { name: `Delete trigger ${databaseTriggerName}` }).click()
        await expect(
          page.getByText(`Successfully removed ${databaseTriggerName}`),
          'Delete confirmation toast should be visible'
        ).toBeVisible({ timeout: 50000 })
      }

      // create new trigger
      await page.getByRole('button', { name: 'New trigger' }).first().click()
      await page.getByRole('textbox', { name: 'Name of trigger' }).fill(databaseTriggerName)
      await page.getByRole('combobox').first().click()
      await page.getByRole('option', { name: `public.${databaseTableName}`, exact: true }).click()
      await page.getByRole('checkbox').first().click()
      await page.getByRole('checkbox').nth(1).click()
      await page.getByRole('checkbox').nth(2).click()
      await page.getByRole('button', { name: 'Choose a function to trigger' }).click()
      await page.getByRole('paragraph').filter({ hasText: 'subscription_check_filters' }).click()
      const triggerCreateWait = createApiResponseWaiter(
        page,
        'pg-meta',
        ref,
        'query?key=trigger-create'
      )
      await page.getByRole('button', { name: /^(Create|Save) trigger$/ }).click()

      // validate trigger creation
      await triggerCreateWait
      await expect(
        page.getByText(`Successfully created trigger`),
        'Trigger creation confirmation toast should be visible'
      ).toBeVisible({
        timeout: 50000,
      })
      const triggerRow = await page.getByRole('row', { name: databaseTriggerName })
      expect(triggerRow).toContainText('subscription_check_filters')
      expect(triggerRow).toContainText(databaseTriggerName)

      // update trigger
      await triggerRow.getByRole('button', { name: 'More options' }).click()
      await page.getByRole('menuitem', { name: 'Edit trigger' }).click()
      await page.getByRole('textbox', { name: 'Name of trigger' }).fill(databaseTriggerNameUpdated)
      const triggerUpdateWait = createApiResponseWaiter(
        page,
        'pg-meta',
        ref,
        'query?key=trigger-update'
      )
      await page.getByRole('button', { name: /^(Create|Save) trigger$/ }).click()

      // validate trigger update
      await triggerUpdateWait
      await expect(
        page.getByText(`Successfully updated trigger`),
        'Trigger updated confirmation toast should be visible'
      ).toBeVisible({
        timeout: 50000,
      })
      const updatedTriggerRow = page.getByRole('row', { name: databaseTriggerNameUpdated })
      await expect(updatedTriggerRow).toContainText('subscription_check_filters')
      await expect(updatedTriggerRow).toContainText(databaseTriggerNameUpdated)

      // delete trigger
      await updatedTriggerRow.getByRole('button', { name: 'More options' }).click()
      await page.getByRole('menuitem', { name: 'Delete trigger' }).click()
      await page.getByPlaceholder('Type in name of trigger').fill(databaseTriggerNameUpdated)
      await page
        .getByRole('button', { name: `Delete trigger ${databaseTriggerNameUpdated}` })
        .click()
      await expect(
        page.getByText(`Successfully removed ${databaseTriggerNameUpdated}`),
        'Delete confirmation toast should be visible'
      ).toBeVisible({
        timeout: 50000,
      })
    })
  })

  test.describe('Database Indexes', () => {
    test('actions works as expected', async ({ page, ref }) => {
      await page.goto(toUrl(`/project/${env.PROJECT_REF}/database/indexes?schema=public`))

      // Wait for database indexes to be populated
      await waitForApiResponse(page, 'pg-meta', ref, 'query?key=indexes-public')

      // create new index button exists in public schema
      await expect(page.getByRole('button', { name: 'Create index' })).toBeVisible()

      // change schema -> auth
      await page.getByTestId('schema-selector').click()
      await page.getByPlaceholder('Find schema...').fill('auth')
      await page.getByRole('option', { name: 'auth' }).click()
      await page.waitForTimeout(2000)

      const ssoProvidersPkeyRow = page.getByRole('row', { name: 'sso_providers_pkey' })
      const confirmationTokenIdxRow = page.getByRole('row', { name: 'confirmation_token_idx' })
      const createIndexButton = page.getByRole('button', { name: 'Create index' }).first()

      expect(ssoProvidersPkeyRow).toBeVisible()
      expect(confirmationTokenIdxRow).toBeVisible()
      // create new index button does not exist in other schemas
      expect(createIndexButton).not.toBeVisible()

      // filter by querying
      await page.getByRole('textbox', { name: 'Search for an index' }).fill('users')
      await page.waitForTimeout(2000)

      expect(page.getByText('sso_providers_pkey')).not.toBeVisible()
      expect(page.getByText('confirmation_token_idx')).toBeVisible()

      // check index definition
      await page
        .getByRole('row', { name: 'confirmation_token_idx' })
        .getByRole('button')
        .last()
        .click()
      await page.getByText('Index:confirmation_token_idx')
      await page.waitForTimeout(2000) // wait for text content to be visible
      expect(await page.getByRole('presentation').textContent()).toBe(
        `CREATE UNIQUE INDEX confirmation_token_idx ON auth.users USING btree (confirmation_token) WHERE ((confirmation_token)::text !~ '^[0-9 ]*$'::text)`
      )
    })

    test('CRUD operations works as expected', async ({ page, ref }) => {
      await page.goto(toUrl(`/project/${env.PROJECT_REF}/database/indexes?schema=public`))

      // Wait for database indexes to be populated
      await waitForApiResponse(page, 'pg-meta', ref, 'query?key=indexes-public')

      // delete index if exist
      const exists = (await page.getByRole('button', { name: databaseIndexName }).count()) > 0
      if (exists) {
        await page.getByRole('button', { name: databaseIndexName }).getByRole('button').click()
        await page.getByRole('menuitem', { name: 'Delete' }).click()
        await page.getByRole('button', { name: 'Confirm' }).click()
        await expect(
          page.getByText(`Successfully deleted role: ${databaseIndexName}`),
          'Delete confirmation toast should be visible'
        ).toBeVisible({ timeout: 50000 })
      }

      // create new index
      await page.getByRole('button', { name: 'Create index' }).click()
      await page.getByRole('button', { name: 'Choose a table' }).click()
      await page.getByRole('option', { name: databaseTableName, exact: true }).click()
      await page.getByText('Choose which columns to create an index on').click()
      await page.getByRole('option', { name: databaseColumnName }).click()
      await page.getByRole('button', { name: 'Create index' }).click()
      await expect(
        page.getByText(`Successfully created index`),
        'Index creation confirmation toast should be visible.'
      ).toBeVisible({ timeout: 50000 })
      await expect(
        page.getByText(`${databaseTableName}_${databaseColumnName}_idx`, { exact: true })
      ).toBeVisible()

      // check index definition
      const newIndexRow = await page.getByRole('row', {
        name: `${databaseTableName}_${databaseColumnName}_idx`,
      })
      await newIndexRow.getByRole('button', { name: 'View definition' }).click()
      await page.waitForTimeout(500) // wait for text content to be visible
      expect(await page.getByRole('presentation').textContent()).toBe(
        `CREATE INDEX ${databaseTableName}_${databaseColumnName}_idx ON public.${databaseTableName} USING btree (${databaseColumnName})`
      )
      await page.getByRole('button', { name: 'Cancel' }).click()

      // delete the index
      await newIndexRow.getByRole('button', { name: 'Delete index' }).click()
      const indexDeleteWait = createApiResponseWaiter(page, 'pg-meta', ref, 'query?key=indexes')
      await page.getByRole('button', { name: 'Confirm delete' }).click()
      await indexDeleteWait
      await expect(
        page.getByText('Successfully deleted index'),
        'Index deletion confirmation toast should be visible'
      ).toBeVisible({ timeout: 50000 })
    })
  })

  test.describe('Roles', () => {
    test('actions works as expected', async ({ page, ref }) => {
      await page.goto(toUrl(`/project/${env.PROJECT_REF}/database/roles`))

      // Wait for database roles list to be populated
      await waitForApiResponse(page, 'pg-meta', ref, 'query?key=database-roles')

      // filter between active and all roles
      await page.getByRole('button', { name: 'Active roles' }).click()
      await expect(page.getByRole('button', { name: 'supabase_admin' })).toBeVisible()
      await expect(page.getByRole('button', { name: 'authenticator' })).toBeVisible()

      // filter by querying
      await page.getByRole('textbox', { name: 'Search for a role' }).fill('supabase')
      await expect(page.getByRole('button', { name: 'supabase_admin' })).toBeVisible()
      await expect(page.getByRole('button', { name: 'authenticator' })).not.toBeVisible()
    })

    test('CRUD operations works as expected', async ({ page, ref }) => {
      await page.goto(toUrl(`/project/${env.PROJECT_REF}/database/roles`))

      // Wait for database roles to be populated
      await waitForApiResponse(page, 'pg-meta', ref, 'query?key=database-roles')

      // delete role if exists
      const exists = (await page.getByRole('button', { name: databaseRoleName }).count()) > 0
      if (exists) {
        await page.getByRole('button', { name: databaseRoleName }).getByRole('button').click()
        await page.getByRole('menuitem', { name: 'Delete' }).click()
        await page.getByRole('button', { name: 'Confirm' }).click()
        await expect(
          page.getByText(`Successfully deleted role: ${databaseRoleName}`),
          'Delete confirmation toast should be visible'
        ).toBeVisible({ timeout: 50000 })
      }

      // create new role
      await page.getByRole('button', { name: 'Add role' }).click()
      await page.getByRole('textbox', { name: 'Name' }).fill(databaseRoleName)
      await page.getByRole('switch').nth(0).click()
      await page.getByRole('switch').nth(1).click()
      await page.getByRole('switch').nth(2).click()
      await page.getByRole('button', { name: 'Save' }).click()
      await expect(
        page.getByText(`Successfully created new role: ${databaseRoleName}`),
        'Create confirmation toast should be visible'
      ).toBeVisible({ timeout: 50000 })

      // delete a role
      await page.getByRole('button', { name: databaseRoleName }).getByRole('button').click()
      await page.getByRole('menuitem', { name: 'Delete' }).click()
      const roleDeleteWait = createApiResponseWaiter(page, 'pg-meta', ref, 'query?key=roles-delete')
      await page.getByRole('button', { name: 'Confirm' }).click()
      await roleDeleteWait
      await expect(
        page.getByText(`Successfully deleted role: ${databaseRoleName}`),
        'Delete confirmation toast should be visible'
      ).toBeVisible({ timeout: 50000 })
    })
  })
})

test.describe.serial('Database Enumerated Types', () => {
  test('actions works as expected', async ({ page, ref }) => {
    await page.goto(toUrl(`/project/${env.PROJECT_REF}/database/types?schema=public`))

    // Wait for database enumerated types to be populated
    // await waitForApiResponse(page, 'pg-meta', ref, 'query?key=schemas')
    await page.waitForLoadState('networkidle')

    // create new type button exists in public schema
    await expect(page.getByRole('button', { name: 'Create type' })).toBeVisible()

    // filter by schema
    await page.getByTestId('schema-selector').click()
    await page.getByPlaceholder('Find schema...').fill('auth')
    await page.getByRole('option', { name: 'auth' }).click()

    await expect(page.getByText('factor_type')).toBeVisible()
    await expect(page.getByText('code_challenge_method')).toBeVisible()
    // create new type button does not exist in other schemas
    await expect(page.getByRole('button', { name: 'Create type' })).not.toBeVisible()

    // filter by querying
    await page.getByRole('textbox', { name: 'Search for a type' }).fill('code')
    await page.waitForTimeout(1000) // wait for enum types to be loaded
    await expect(page.getByText('factor_type')).not.toBeVisible()
    await expect(page.getByText('code_challenge_method')).toBeVisible()
  })

  test('CRUD operations works as expected', async ({ page, ref }) => {
    const wait = createApiResponseWaiter(page, 'pg-meta', ref, 'query?key=schemas')
    await page.goto(toUrl(`/project/${env.PROJECT_REF}/database/types?schema=public`))

    // Wait for database roles list to be populated
    await wait
    // await page.waitForLoadState('networkidle')

    // if enum exists, delete it.
    await page.waitForTimeout(500)
    if ((await page.getByRole('cell', { name: databaseEnumName, exact: true }).count()) > 0) {
      await page
        .getByRole('row', { name: `public ${databaseEnumName}` })
        .getByRole('button')
        .click()
      await page.getByRole('menuitem', { name: 'Delete type' }).click()
      await page.getByRole('heading', { name: 'Confirm to delete enumerated' }).click()
      await page.getByRole('button', { name: 'Confirm delete' }).click()
      await expect(page.getByText(`Successfully deleted "${databaseEnumName}"`)).toBeVisible()
    }

    // create a new enum
    await page.getByRole('button', { name: 'Create type' }).click()
    await page.getByRole('textbox', { name: 'Name' }).fill(databaseEnumName)
    await page.getByRole('button', { name: 'Create type' }).click()
    await page.locator('input[name="values.0.value"]').fill(databaseEnumValue1Name)
    await page.getByRole('button', { name: 'Add value' }).click()
    await page.locator('input[name="values.1.value"]').fill(databaseEnumValue2Name)
    const enumCreateWait = createApiResponseWaiter(page, 'pg-meta', ref, 'types')
    await page.getByRole('button', { name: 'Create type' }).click()

    // Wait for enum response to be completed and validate it
    await enumCreateWait
    const enumRow = page.getByRole('row', { name: `${databaseEnumName}` })
    await expect(enumRow).toContainText(databaseEnumName)
    await expect(enumRow).toContainText(`${databaseEnumValue1Name}, ${databaseEnumValue2Name}`)

    // update enum
    await enumRow.getByRole('button').click()
    await page.getByRole('menuitem', { name: 'Update type' }).click()
    await page.getByRole('button', { name: 'Add value' }).click()
    await page.locator('input[name="values.2.updatedValue"]').fill(databaseEnumValue3Name)
    await page.getByRole('button', { name: 'Update type' }).click()
    const updatedEnumRow = page.getByRole('row', { name: `${databaseEnumName}` })
    await expect(updatedEnumRow).toContainText(
      `${databaseEnumValue1Name}, ${databaseEnumValue2Name}, ${databaseEnumValue3Name}`
    )

    // delete enum
    await updatedEnumRow.getByRole('button').click()
    await page.getByRole('menuitem', { name: 'Delete type' }).click()
    await page.getByRole('heading', { name: 'Confirm to delete enumerated' }).click()
    await page.getByRole('button', { name: 'Confirm delete' }).click()
    await expect(page.getByText(`Successfully deleted "${databaseEnumName}"`)).toBeVisible({
      timeout: 50000,
    })
  })
})

test.describe.serial('Database Functions', () => {
  test('actions works as expected', async ({ page, ref }) => {
    await page.goto(toUrl(`/project/${env.PROJECT_REF}/database/functions?schema=public`))

    // Wait for database functions to be populated
    await page.waitForLoadState('networkidle')
    // await waitForApiResponse(page, 'pg-meta', ref, 'query?key=database-functions')

    // create a new function button exists in public schema
    await expect(page.getByRole('button', { name: 'Create a new function' })).toBeVisible()

    // change schema -> auth
    await page.getByTestId('schema-selector').click()
    await page.getByPlaceholder('Find schema...').fill('auth')
    await page.getByRole('option', { name: 'auth' }).click()
    await expect(page.getByText('email')).toBeVisible()
    await expect(page.getByText('jwt')).toBeVisible()
    // create a new function button does not exist in other schemas
    await expect(page.getByRole('button', { name: 'Create a new function' })).not.toBeVisible()

    // filter by querying
    await page.getByRole('textbox', { name: 'Search for a function' }).fill('email')
    await page.waitForTimeout(500) // wait for enum types to be loaded
    await expect(page.getByText('email')).toBeVisible()
    await expect(page.getByText('jwt')).not.toBeVisible()
  })

  test('CRUD operations works as expected', async ({ page, ref }) => {
    await page.goto(toUrl(`/project/${env.PROJECT_REF}/database/functions?schema=public`))

    // Wait for database functions to be populated
    // await waitForApiResponse(page, 'pg-meta', ref, 'query?key=database-functions')
    await page.waitForLoadState('networkidle')

    // delete function if exists
    if ((await page.getByRole('button', { name: databaseFunctionName }).count()) > 0) {
      const functionRow = await page.getByRole('row', { name: databaseFunctionName })
      await functionRow.getByRole('button', { name: 'More options' }).click()
      await page.getByRole('menuitem', { name: 'Delete function' }).click()
      await page
        .getByRole('textbox', { name: `Type ${databaseFunctionName} to confirm.` })
        .fill(databaseFunctionName)
      await page.getByRole('button', { name: `Delete function ${databaseFunctionName}` }).click()
      await expect(
        page.getByText(`Successfully removed function ${databaseFunctionName}`),
        'Delete confirmation toast should be visible'
      ).toBeVisible({
        timeout: 50000,
      })
    }

    // create new function
    await page.getByRole('button', { name: 'Create a new function' }).click()
    await page.getByRole('textbox', { name: 'Name of function' }).fill(databaseFunctionName)
    const editor = await page.getByRole('presentation')
    await editor.click()
    await page.keyboard.type(`BEGIN
END;`)
    await page.waitForTimeout(500) // wait for text content to be visible
    expect(await page.getByRole('presentation').textContent()).toBe(`BEGINEND;`)
    const functionCreateWait = createApiResponseWaiter(
      page,
      'pg-meta',
      ref,
      'query?key=functions-create'
    )
    const functionCreateRefreshWait = createApiResponseWaiter(
      page,
      'pg-meta',
      ref,
      'query?key=database-functions'
    )
    await page.getByRole('button', { name: 'Create function' }).click()

    // validate function creation
    await functionCreateWait
    await functionCreateRefreshWait
    await expect(
      page.getByText(`Successfully created function`),
      'Trigger creation confirmation toast should be visible'
    ).toBeVisible({
      timeout: 50000,
    })
    const functionRow = await page.getByRole('row', { name: databaseFunctionName })
    expect(functionRow).toContainText(databaseFunctionName)

    // update function
    await functionRow.getByRole('button', { name: 'More options' }).click()
    await page.getByRole('menuitem', { name: 'Edit function', exact: true }).click()
    await page.getByRole('textbox', { name: 'Name of function' }).fill(databaseFunctionNameUpdated)
    const functionUpdateWait = createApiResponseWaiter(
      page,
      'pg-meta',
      ref,
      'query?key=functions-update'
    )
    await page.getByRole('button', { name: 'Save function' }).click()

    // validate function update
    await functionUpdateWait
    await expect(
      page.getByText(`Successfully updated function ${databaseFunctionNameUpdated}`),
      'Function updated confirmation toast should be visible'
    ).toBeVisible({
      timeout: 50000,
    })
    const updatedFunctionRow = page.getByRole('row', { name: databaseFunctionNameUpdated })
    await expect(updatedFunctionRow).toContainText(databaseFunctionNameUpdated)

    // delete function
    await updatedFunctionRow.getByRole('button', { name: 'More options' }).click()
    await page.getByRole('menuitem', { name: 'Delete function' }).click()
    await page.getByPlaceholder('Type in name of function').fill(databaseFunctionNameUpdated)
    const functionDeleteWait = createApiResponseWaiter(
      page,
      'pg-meta',
      ref,
      'query?key=functions-delete'
    )
    await page
      .getByRole('button', { name: `Delete function ${databaseFunctionNameUpdated}` })
      .click()
    await functionDeleteWait
    await expect(
      page.getByText(`Successfully removed function ${databaseFunctionNameUpdated}`),
      'Delete confirmation toast should be visible'
    ).toBeVisible({
      timeout: 50000,
    })
  })
})


--- e2e/studio/utils/dismiss-toast.ts ---
import { Page } from '@playwright/test'

export const dismissToast = async (page: Page) => {
  await page
    .locator('li.toast')
    .getByRole('button', { name: 'Opt out' })
    .waitFor({ state: 'visible' })
  await page.locator('li.toast').getByRole('button', { name: 'Opt out' }).click()
}

export const toKebabCase = (str: string) => str.replace(/([A-Z])/g, '-$1').toLowerCase()

export const dismissToastsIfAny = async (page: Page) => {
  const closeButtons = page.getByRole('button', { name: 'Close toast' })
  const count = await closeButtons.count()
  for (let i = 0; i < count; i++) {
    await closeButtons.nth(i).click()
  }
}


--- e2e/studio/features/_global.setup.ts ---
import { test as setup } from '@playwright/test'
import dotenv from 'dotenv'
import path from 'path'
import { env } from '../env.config.js'
import { setupProjectForTests } from '../scripts/setup-platform-tests.js'
import { loginWithEmail } from '../scripts/login/email.js'
import { loginWithGithubWithRetry } from '../scripts/login/github.js'

/**
 * Run any setup tasks for the tests.
 * Catch errors and show useful messages.
 */

dotenv.config({
  path: path.resolve(import.meta.dirname, '..', '.env.local'),
  override: true,
})

const IS_PLATFORM = process.env.IS_PLATFORM
const doAuthentication = env.AUTHENTICATION

setup('Global Setup', async ({ page }) => {
  console.log(`\n 🧪 Setting up test environment.
    - Studio URL: ${env.STUDIO_URL}
    - API URL: ${env.API_URL}
    - Auth: ${doAuthentication ? 'enabled' : 'disabled'}
    - Is Platform: ${IS_PLATFORM}
    `)

  /**
   * Studio Check
   */

  const studioUrl = env.STUDIO_URL
  const apiUrl = env.API_URL

  await page.goto(studioUrl).catch((err) => {
    console.error(
      `\n 🚨 Setup Error 
Studio is not available at: ${studioUrl}

Please ensure:
  1. Studio is running in the expected URL
  2. You have proper network access
`
    )
    throw err
  })

  console.log(`\n ✅ Studio is running at ${studioUrl}`)

  /**
   * API Check
   */

  await fetch(apiUrl).catch((err) => {
    console.error(`\n 🚨 Setup Error
API is not available at: ${apiUrl}

Please ensure:
  1. API is running in the expected URL
  2. You have proper network access

To start API locally, run:
  npm run dev:api`)
    throw new Error('API is not available')
  })

  console.log(`\n ✅ API is running at ${apiUrl}`)

  /**
   * Setup Project for tests
   */
  const projectRef = await setupProjectForTests()
  process.env.PROJECT_REF = projectRef
  env.PROJECT_REF = projectRef

  /**
   * Only run authentication if the environment requires it
   */
  if (!doAuthentication) {
    console.log(`\n 🔑 Skipping authentication for ${env.STUDIO_URL}`)
    return
  }

  const { EMAIL, PASSWORD } = env
  if (EMAIL && PASSWORD) {
    console.log(`\n 🔑 Authenticating user with email and password`)

    try {
      await loginWithEmail(page, studioUrl, {
        email: EMAIL,
        password: PASSWORD,
      })
      console.log(`\n ✅ Successfully authenticated with email`)
      return
    } catch (err) {
      console.error(`\n 🚨 Authentication failed with email/password`)
      throw err
    }
  }

  const { GITHUB_USER, GITHUB_PASS, GITHUB_TOTP } = env
  if (GITHUB_USER && GITHUB_PASS && GITHUB_TOTP) {
    console.log(`\n 🔑 Authenticating user with GitHub`)
    try {
      await loginWithGithubWithRetry({
        page,
        githubTotp: GITHUB_TOTP,
        githubUser: GITHUB_USER,
        githubPass: GITHUB_PASS,
        supaDashboard: studioUrl,
      })
      console.log(`\n ✅ Successfully authenticated with GitHub`)
      return
    } catch (err) {
      console.error(`\n 🚨 Authentication failed with GitHub`)
      throw err
    }
  }
})


--- e2e/studio/features/home.spec.ts ---
import { expect } from '@playwright/test'
import { test } from '../utils/test.js'
import { toUrl } from '../utils/to-url.js'

test.describe('Project', async () => {
  test('Can navigate to project home page', async ({ page, ref }) => {
    await page.goto(toUrl(`/project/${ref}`))

    // The home page has 2 variants (classic and new). Both render an H1 heading.
    // Assert on a stable, variant-agnostic selector.
    await expect(page.getByRole('heading', { level: 1 })).toBeVisible()
  })
})


--- i18n/README.ar.md ---
<div style="direction: rtl;" dir="rtl">

<p align="center">
<img src="https://user-images.githubusercontent.com/8291514/213727234-cda046d6-28c6-491a-b284-b86c5cede25d.png#gh-light-mode-only">
<img src="https://user-images.githubusercontent.com/8291514/213727225-56186826-bee8-43b5-9b15-86e839d89393.png#gh-dark-mode-only">
</p>

---

# Supabase

[Supabase](https://supabase.com)هو بديل مفتوح المصدر لـ(Firebase). نحن نبني ميزات (Firebase) باستخدام أدوات مفتوحة المصدر عالية الجودة تستخدمها الشركات.

- [x] قاعدة بيانات (Postgres) مستضافة. [الشرح](https://supabase.com/docs/guides/database)
- [x] [الشرح](https://supabase.com/docs/guides/auth) المصادقة والترخيص
- [x] واجهات برمجة التطبيقات التي يتم إنشاؤها تلقائيا.
  - [x] REST. [الشرح](https://supabase.com/docs/guides/api)
  - [x] GraphQL. [الشرح](https://supabase.com/docs/guides/graphql)
  - [x] اشتراكات الوقت الفعلي (Realtime subscriptions). [الشرح](https://supabase.com/docs/guides/realtime)
- [x] الدوال.
  - [x] دوال قاعدة البيانات (Database Functions). [الشرح](https://supabase.com/docs/guides/database/functions)
  - [x] Edge Functions [الشرح](https://supabase.com/docs/guides/functions)
- [x] [الشرح](https://supabase.com/docs/guides/storage) التخزين.
- [x] ذكاء اصطناعي + مجموعة أدوات المتجهات/التضمينات (AI + Vector/Embeddings Toolkit). [الشرح](https://supabase.com/docs/guides/ai)
- [x] لوحة الإدارة.

![Supabase Dashboard](https://raw.githubusercontent.com/supabase/supabase/master/apps/www/public/images/github/supabase-dashboard.png)

شاهد "الإصدارات" من هذا المشروع للحصول على إشعار بالتحديثات الرئيسية.

<kbd><img src="https://raw.githubusercontent.com/supabase/supabase/d5f7f413ab356dc1a92075cb3cee4e40a957d5b1/web/static/watch-repo.gif" alt="Watch this repo"/></kbd>

## الشرح

للحصول على الشرح الكامل، قم بزيارة [supabase.com/docs](https://supabase.com/docs).

لمعرفه كيفية دعم المشروع قم بزيارة [Getting Started](./DEVELOPERS.md).

## المجتمع والدعم

- [منتدى المجتمع](https://github.com/supabase/supabase/discussions). الأفضل لـ: المساعدة في البناء، والنقاش حول أفضل ممارسات قاعدة البيانات.
- [مشاكل GitHub](https://github.com/supabase/supabase/issues). الأفضل لـ: المشاكل والأخطاء التي تواجهها عند استخدامك لـ(Supabase).
- [دعم البريد الإلكتروني](https://supabase.com/docs/support#business-support). الأفضل لـ: مشاكل مع قاعدة بياناتك أو البنية التحتية.
- [ديسكورد](https://discord.supabase.com/). الأفضل لـ: مشاركة التطبيقات الخاصه بك وقضاء بعض الوقت مع المجتمع.

## كيف يعمل (Supabase)؟

Supabase عبارة عن مجموعة من الأدوات مفتوحة المصدر. نحن نبني ميزات (Firebase) باستخدام أدوات مفتوحة المصدر عالية الجودة تستخدمها الشركات. إذا كانت الأدوات والمجتمعات موجودة ، باستخدام MIT أو Apache 2 أو ترخيص مفتوح مكافئ ، فسنستخدم هذه الأداة وندعمها. إذا لم تكن الأداة موجودة ، فإننا نبنيها ونفتح مصدرها بأنفسنا. (Supabase) ليس تعيين 1 إلى 1 لـ(Firebase). هدفنا هو منح المطورين تجربة مطور تشبه (Firebase) باستخدام أدوات مفتوحة المصدر.

**الهيكلة الحالية**

(supabase) هي [منصة مستضافة](https://supabase.com/dashboard), يمكنك التسجيل والبدأ باستخدامها دون الحاجة لتثبيت أي شئ. يمكنك أيضا [استضافتها ذاتيا](https://supabase.com/docs/guides/hosting/overview) و [تطويرها داخليا](https://supabase.com/docs/guides/local-development).

![Architecture](https://github.com/supabase/supabase/blob/master/apps/docs/public/img/supabase-architecture.svg)

- [PostgreSQL](https://www.postgresql.org/) هي قاعدة بيانات قائمة على العلاقات الشيئية مع ٣٠ سنة من التطوير النشط التي اكسبتها سمعة وموثقية قوية وتمتاز بالمتانة والأداء.
- [Realtime](https://github.com/supabase/realtime) هو خادم بلغة (Elixir) يمكنك من الاستماع لقاعدة البيانات لأي تغيرات سواء أنشاء أو تعديل أو مسح باستخدام ال(websocket).
- [PostgREST](http://postgrest.org/) هو خادم ويب يستطيع تحويل قاعدة بيانات PostgreSQL مباشرة ألي RESTful API
- [Storage](https://github.com/supabase/storage-api) يقدم واجهة RESTful لأدارة الملفات المخزنة فس S3, باستخدام Postgres لأدارة الصلاحيات
- [postgres-meta](https://github.com/supabase/postgres-meta) هو RESTful API لأدارة قاعدة البيانات الخاصة بك, تمكنك من الإستعلام عن الجداول, إضافة أدوار (مفرد دور), وتشغيل الأوامر.. الخ
- [GoTrue](https://github.com/netlify/gotrue) هو API مبني على SWT لأدارة المستخدمين وإنشاء رمز SWT.
- [Kong](https://github.com/Kong/kong) هو بوابة API لـcloud-native

#### مكتبات العميل

مكتباتنا معيارية. كل مكتبة فرعية هي تطبيق مستقل لنظام خارجي واحد. هذه إحدى الطرق التي ندعم بها الأدوات الحالية.

<table style="table-layout:fixed; white-space: nowrap;">
  <tr>
    <th>اللغة</th>
    <th>العميل</th>
    <th colspan="4">مميزات العميل</th>
  </tr>
  <tr>
    <th></th>
    <th>Supabase</th>
    <th><a href="https://github.com/postgrest/postgrest" target="_blank" rel="noopener noreferrer">PostgREST</a></th>
    <th><a href="https://github.com/supabase/gotrue" target="_blank" rel="noopener noreferrer">GoTrue</a></th>
    <th><a href="https://github.com/supabase/realtime" target="_blank" rel="noopener noreferrer">Realtime</a></th>
    <th><a href="https://github.com/supabase/storage-api" target="_blank" rel="noopener noreferrer">Storage</a></th>
  </tr>
  <!-- TEMPLATE FOR NEW ROW -->
  <!-- START ROW
  <tr>
    <td>lang</td>
    <td><a href="https://github.com/supabase-community/supabase-lang" target="_blank" rel="noopener noreferrer">supabase-lang</a></td>
    <td><a href="https://github.com/supabase-community/postgrest-lang" target="_blank" rel="noopener noreferrer">postgrest-lang</a></td>
    <td><a href="https://github.com/supabase-community/gotrue-lang" target="_blank" rel="noopener noreferrer">gotrue-lang</a></td>
    <td><a href="https://github.com/supabase-community/realtime-lang" target="_blank" rel="noopener noreferrer">realtime-lang</a></td>
    <td><a href="https://github.com/supabase-community/storage-lang" target="_blank" rel="noopener noreferrer">storage-lang</a></td>
  </tr>
  END ROW -->
  <th colspan="6">⚡️ الرسمي ⚡️</th>
  <tr>
    <td>JavaScript (TypeScript)</td>
    <td><a href="https://github.com/supabase/supabase-js" target="_blank" rel="noopener noreferrer">supabase-js</a></td>
    <td><a href="https://github.com/supabase/supabase-js/tree/master/packages/core/postgrest-js" target="_blank" rel="noopener noreferrer">postgrest-js</a></td>
    <td><a href="https://github.com/supabase/supabase-js/tree/master/packages/core/auth-js" target="_blank" rel="noopener noreferrer">auth-js</a></td>
    <td><a href="https://github.com/supabase/supabase-js/tree/master/packages/core/realtime-js" target="_blank" rel="noopener noreferrer">realtime-js</a></td>
    <td><a href="https://github.com/supabase/supabase-js/tree/master/packages/core/storage-js" target="_blank" rel="noopener noreferrer">storage-js</a></td>
  </tr>
  <th colspan="6">💚 المجتمعي 💚</th>
  <tr>
    <td>C#</td>
    <td><a href="https://github.com/supabase-community/supabase-csharp" target="_blank" rel="noopener noreferrer">supabase-csharp</a></td>
    <td><a href="https://github.com/supabase-community/postgrest-csharp" target="_blank" rel="noopener noreferrer">postgrest-csharp</a></td>
    <td><a href="https://github.com/supabase-community/gotrue-csharp" target="_blank" rel="noopener noreferrer">gotrue-csharp</a></td>
    <td><a href="https://github.com/supabase-community/realtime-csharp" target="_blank" rel="noopener noreferrer">realtime-csharp</a></td>
    <td>-</td>
  </tr>
  <tr>
    <td>Flutter</td>
    <td><a href="https://github.com/supabase/supabase-flutter" target="_blank" rel="noopener noreferrer">supabase-dart</a></td>
    <td><a href="https://github.com/supabase/postgrest-dart" target="_blank" rel="noopener noreferrer">postgrest-dart</a></td>
    <td><a href="https://github.com/supabase/gotrue-dart" target="_blank" rel="noopener noreferrer">gotrue-dart</a></td>
    <td><a href="https://github.com/supabase/realtime-dart" target="_blank" rel="noopener noreferrer">realtime-dart</a></td>
    <td><a href="https://github.com/supabase/storage-dart" target="_blank" rel="noopener noreferrer">storage-dart</a></td>
  </tr>
  <tr>
    <td>Go</td>
    <td>-</td>
    <td><a href="https://github.com/supabase-community/postgrest-go" target="_blank" rel="noopener noreferrer">postgrest-go</a></td>
    <td>-</td>
    <td>-</td>
    <td>-</td>
  </tr>
  <tr>
    <td>Java</td>
    <td>-</td>
    <td>-</td>
    <td><a href="https://github.com/supabase-community/gotrue-java" target="_blank" rel="noopener noreferrer">gotrue-java</a></td>
    <td>-</td>
    <td>-</td>
  </tr>
  <tr>
    <td>Kotlin</td>
    <td><a href="https://github.com/supabase-community/supabase-kt" target="_blank" rel="noopener noreferrer">supabase-kt</a></td>
    <td><a href="https://github.com/supabase-community/supabase-kt/tree/master/Postgrest" target="_blank" rel="noopener noreferrer">postgrest-kt</a></td>
    <td><a href="https://github.com/supabase-community/supabase-kt/tree/master/GoTrue" target="_blank" rel="noopener noreferrer">gotrue-kt</a></td>
    <td><a href="https://github.com/supabase-community/supabase-kt/tree/master/Realtime" target="_blank" rel="noopener noreferrer">realtime-kt</a></td>
    <td><a href="https://github.com/supabase-community/supabase-kt/tree/master/Storage" target="_blank" rel="noopener noreferrer">storage-kt</a></td>
  </tr>
  <tr>
    <td>Python</td>
    <td><a href="https://github.com/supabase-community/supabase-py" target="_blank" rel="noopener noreferrer">supabase-py</a></td>
    <td><a href="https://github.com/supabase-community/postgrest-py" target="_blank" rel="noopener noreferrer">postgrest-py</a></td>
    <td><a href="https://github.com/supabase-community/gotrue-py" target="_blank" rel="noopener noreferrer">gotrue-py</a></td>
    <td><a href="https://github.com/supabase-community/realtime-py" target="_blank" rel="noopener noreferrer">realtime-py</a></td>
    <td>-</td>
  </tr>
  <tr>
    <td>Ruby</td>
    <td><a href="https://github.com/supabase-community/supabase-rb" target="_blank" rel="noopener noreferrer">supabase-rb</a></td>
    <td><a href="https://github.com/supabase-community/postgrest-rb" target="_blank" rel="noopener noreferrer">postgrest-rb</a></td>
    <td>-</td>
    <td>-</td>
    <td>-</td>
  </tr>
  <tr>
    <td>Rust</td>
    <td>-</td>
    <td><a href="https://github.com/supabase-community/postgrest-rs" target="_blank" rel="noopener noreferrer">postgrest-rs</a></td>
    <td>-</td>
    <td>-</td>
    <td>-</td>
  </tr>
  <tr>
    <td>Swift</td>
    <td><a href="https://github.com/supabase-community/supabase-swift" target="_blank" rel="noopener noreferrer">supabase-swift</a></td>
    <td><a href="https://github.com/supabase-community/postgrest-swift" target="_blank" rel="noopener noreferrer">postgrest-swift</a></td>
    <td><a href="https://github.com/supabase-community/gotrue-swift" target="_blank" rel="noopener noreferrer">gotrue-swift</a></td>
    <td><a href="https://github.com/supabase-community/realtime-swift" target="_blank" rel="noopener noreferrer">realtime-swift</a></td>
    <td><a href="https://github.com/supabase-community/storage-swift" target="_blank" rel="noopener noreferrer">storage-swift</a></td>
  </tr>
</table>
## الترجمات

- [قائمة الترجمات](/i18n/languages.md) <!--- Keep only this -->

## الرعاة

[![New Sponsor](https://user-images.githubusercontent.com/10214025/90518111-e74bbb00-e198-11ea-8f88-c9e3c1aa4b5b.png)](https://github.com/sponsors/supabase)

</div>


## Links discovered
- [Supabase](https://supabase.com)
- [الشرح](https://supabase.com/docs/guides/database)
- [الشرح](https://supabase.com/docs/guides/auth)
- [الشرح](https://supabase.com/docs/guides/api)
- [الشرح](https://supabase.com/docs/guides/graphql)
- [الشرح](https://supabase.com/docs/guides/realtime)
- [الشرح](https://supabase.com/docs/guides/database/functions)
- [الشرح](https://supabase.com/docs/guides/functions)
- [الشرح](https://supabase.com/docs/guides/storage)
- [الشرح](https://supabase.com/docs/guides/ai)
- [Supabase Dashboard](https://raw.githubusercontent.com/supabase/supabase/master/apps/www/public/images/github/supabase-dashboard.png)
- [supabase.com/docs](https://supabase.com/docs)
- [Getting Started](https://github.com/supabase/supabase/blob/master/i18n/DEVELOPERS.md)
- [منتدى المجتمع](https://github.com/supabase/supabase/discussions)
- [مشاكل GitHub](https://github.com/supabase/supabase/issues)
- [دعم البريد الإلكتروني](https://supabase.com/docs/support#business-support)
- [ديسكورد](https://discord.supabase.com/)
- [منصة مستضافة](https://supabase.com/dashboard)
- [استضافتها ذاتيا](https://supabase.com/docs/guides/hosting/overview)
- [تطويرها داخليا](https://supabase.com/docs/guides/local-development)
- [Architecture](https://github.com/supabase/supabase/blob/master/apps/docs/public/img/supabase-architecture.svg)
- [PostgreSQL](https://www.postgresql.org/)
- [Realtime](https://github.com/supabase/realtime)
- [PostgREST](http://postgrest.org/)
- [Storage](https://github.com/supabase/storage-api)
- [postgres-meta](https://github.com/supabase/postgres-meta)
- [GoTrue](https://github.com/netlify/gotrue)
- [Kong](https://github.com/Kong/kong)
- [قائمة الترجمات](https://github.com/supabase/supabase/blob/master/i18n/languages.md)
- [![New Sponsor](https://user-images.githubusercontent.com/10214025/90518111-e74bbb00-e198-11ea-8f88-c9e3c1aa4b5b.png)
- [PostgREST](https://github.com/postgrest/postgrest)
- [GoTrue](https://github.com/supabase/gotrue)
- [supabase-lang](https://github.com/supabase-community/supabase-lang)
- [postgrest-lang](https://github.com/supabase-community/postgrest-lang)
- [gotrue-lang](https://github.com/supabase-community/gotrue-lang)
- [realtime-lang](https://github.com/supabase-community/realtime-lang)
- [storage-lang](https://github.com/supabase-community/storage-lang)
- [supabase-js](https://github.com/supabase/supabase-js)
- [postgrest-js](https://github.com/supabase/supabase-js/tree/master/packages/core/postgrest-js)
- [auth-js](https://github.com/supabase/supabase-js/tree/master/packages/core/auth-js)
- [realtime-js](https://github.com/supabase/supabase-js/tree/master/packages/core/realtime-js)
- [storage-js](https://github.com/supabase/supabase-js/tree/master/packages/core/storage-js)
- [supabase-csharp](https://github.com/supabase-community/supabase-csharp)
- [postgrest-csharp](https://github.com/supabase-community/postgrest-csharp)
- [gotrue-csharp](https://github.com/supabase-community/gotrue-csharp)
- [realtime-csharp](https://github.com/supabase-community/realtime-csharp)
- [supabase-dart](https://github.com/supabase/supabase-flutter)
- [postgrest-dart](https://github.com/supabase/postgrest-dart)
- [gotrue-dart](https://github.com/supabase/gotrue-dart)
- [realtime-dart](https://github.com/supabase/realtime-dart)
- [storage-dart](https://github.com/supabase/storage-dart)
- [postgrest-go](https://github.com/supabase-community/postgrest-go)
- [gotrue-java](https://github.com/supabase-community/gotrue-java)
- [supabase-kt](https://github.com/supabase-community/supabase-kt)
- [postgrest-kt](https://github.com/supabase-community/supabase-kt/tree/master/Postgrest)
- [gotrue-kt](https://github.com/supabase-community/supabase-kt/tree/master/GoTrue)
- [realtime-kt](https://github.com/supabase-community/supabase-kt/tree/master/Realtime)
- [storage-kt](https://github.com/supabase-community/supabase-kt/tree/master/Storage)
- [supabase-py](https://github.com/supabase-community/supabase-py)
- [postgrest-py](https://github.com/supabase-community/postgrest-py)
- [gotrue-py](https://github.com/supabase-community/gotrue-py)
- [realtime-py](https://github.com/supabase-community/realtime-py)
- [supabase-rb](https://github.com/supabase-community/supabase-rb)
- [postgrest-rb](https://github.com/supabase-community/postgrest-rb)
- [postgrest-rs](https://github.com/supabase-community/postgrest-rs)
- [supabase-swift](https://github.com/supabase-community/supabase-swift)
- [postgrest-swift](https://github.com/supabase-community/postgrest-swift)
- [gotrue-swift](https://github.com/supabase-community/gotrue-swift)
- [realtime-swift](https://github.com/supabase-community/realtime-swift)
- [storage-swift](https://github.com/supabase-community/storage-swift)

--- i18n/README.bg.md ---
<p align="center">
<img src="https://user-images.githubusercontent.com/8291514/213727234-cda046d6-28c6-491a-b284-b86c5cede25d.png#gh-light-mode-only">
<img src="https://user-images.githubusercontent.com/8291514/213727225-56186826-bee8-43b5-9b15-86e839d89393.png#gh-dark-mode-only">
</p>

---

# Supabase

[Supabase](https://supabase.com) е алтернатива на Firebase с отворен код. Ние изграждаме функциите на Firebase, като използваме инструменти с отворен код от корпоративен клас.

- [x] Хоствана база данни Postgres. [Документи](https://supabase.com/docs/guides/database)
- [x] Удостоверяване и оторизация. [Документи](https://supabase.com/docs/guides/auth)
- [x] Автоматично генерирани API.
  - [x] REST. [Документи](https://supabase.com/docs/guides/api#rest-api-overview)
  - [x] GraphQL. [Документи](https://supabase.com/docs/guides/api#graphql-api-overview)
  - [x] Абонаменти в реално време. [Документи](https://supabase.com/docs/guides/api#realtime-api-overview)
- [x] Функции.
  - [x] Функции за бази данни. [Docs](https://supabase.com/docs/guides/database/functions)
  - [x] Крайни функции [Docs](https://supabase.com/docs/guides/functions)
- [x] Съхранение на файлове. [Документи](https://supabase.com/docs/guides/storage)
- [x] Информационно табло

![Supabase Dashboard](https://raw.githubusercontent.com/supabase/supabase/master/apps/www/public/images/github/supabase-dashboard.png)

## Документация

За пълна документация посетете [supabase.com/docs](https://supabase.com/docs)

За да видите как да допринасяте, посетете [Getting Started](../DEVELOPERS.md)

## Общност и поддръжка

- [Форум на общността](https://github.com/supabase/supabase/discussions). Най-добре за: помощ при изграждане, обсъждане на най-добрите практики за бази данни.
- [GitHub Issues](https://github.com/supabase/supabase/issues). Най-добре за: грешки и пропуски, които срещате при използването на Supabase.
- [Email Support](https://supabase.com/docs/support#business-support). Най-добре за: проблеми с вашата база данни или инфраструктура.
- [Discord](https://discord.supabase.com). Най-добър за: споделяне на вашите приложения и общуване с общността.

## Статус

- [x] Алфа: Тестваме Supabase със затворен набор от клиенти
- [x] Публична алфа: Всеки може да се регистрира на адрес [supabase.com/dashboard](https://supabase.com/dashboard). Но не се притеснявайте от нас, има няколко проблема
- [x] Публична бета версия: Достатъчно стабилна за повечето случаи на използване извън предприятията
- [ ] Публична: Обща наличност [[статус](https://supabase.com/docs/guides/getting-started/features#feature-status)]

В момента сме в публична бета версия. Следете "releases" на това репо, за да бъдете уведомявани за основни актуализации.

<kbd><img src="https://raw.githubusercontent.com/supabase/supabase/d5f7f413ab356dc1a92075cb3cee4e40a957d5b1/web/static/watch-repo.gif" alt="Watch this repo"/></kbd>

---

## Как работи

Supabase е комбинация от инструменти с отворен код. Ние изграждаме функциите на Firebase, като използваме продукти с отворен код от корпоративен клас. Ако инструментите и общностите съществуват, с MIT, Apache 2 или еквивалентен отворен лиценз, ние ще използваме и поддържаме този инструмент. Ако инструментът не съществува, ние сами го изграждаме и създаваме с отворен код. Supabase не е 1 към 1 съпоставка на Firebase. Нашата цел е да предоставим на разработчиците преживяване, подобно на това на Firebase, като използваме инструменти с отворен код.

**Архитектура**

Supabase е [хоствана платформа](https://supabase.com/dashboard). Можете да се регистрирате и да започнете да използвате Supabase, без да инсталирате нищо.
Можете също така да [самостоятелно хоствате](https://supabase.com/docs/guides/hosting/overview) и [да разработвате локално](https://supabase.com/docs/guides/local-development).

![Архитектура](https://github.com/supabase/supabase/blob/master/apps/docs/public/img/supabase-architecture.svg)

- [PostgreSQL](https://www.postgresql.org/) е обектно-релационна система за бази данни с над 30-годишна активна разработка, която ѝ е спечелила силна репутация за надеждност, устойчивост на функциите и производителност.
- [Realtime](https://github.com/supabase/realtime) е сървър на Elixir, който ви позволява да слушате PostgreSQL вмъквания, актуализации и изтривания, използвайки websockets. Realtime се допитва до вградената функция за репликация на Postgres за промени в базата данни, преобразува промените в JSON, след което излъчва JSON през уебсокети до оторизирани клиенти.
- [PostgREST](http://postgrest.org/) е уеб сървър, който превръща вашата база данни PostgreSQL директно в RESTful API
- [pg_graphql](http://github.com/supabase/pg_graphql/) е разширение на PostgreSQL, което разкрива GraphQL API
- [Storage](https://github.com/supabase/storage-api) предоставя RESTful интерфейс за управление на файлове, съхранявани в S3, като използва Postgres за управление на разрешенията.
- [postgres-meta](https://github.com/supabase/postgres-meta) е RESTful API за управление на вашия Postgres, който ви позволява да извличате таблици, да добавяте роли, да изпълнявате заявки и т.н.
- [GoTrue](https://github.com/netlify/gotrue) е SWT базиран API за управление на потребители и издаване на SWT токени.
- [Kong](https://github.com/Kong/kong) е API шлюз, базиран на облака.

#### Клиентски библиотеки

Нашият подход към клиентските библиотеки е модулен. Всяка подбиблиотека е самостоятелна реализация за една външна система. Това е един от начините, по които поддържаме съществуващите инструменти.

<table style="table-layout:fixed; white-space: nowrap;">
  <tr>
    <th>Език</th>
    <th>Клиент</th>
    <th colspan="5">Функционални клиенти (в комплект с клиента на Supabase)</th>
  </tr>
  
  <tr>
    <th></th>
    <th>Supabase</th>
    <th><a href="https://github.com/postgrest/postgrest" target="_blank" rel="noopener noreferrer">PostgREST</a></th>
    <th><a href="https://github.com/supabase/gotrue" target="_blank" rel="noopener noreferrer">GoTrue</a></th>
    <th><a href="https://github.com/supabase/realtime" target="_blank" rel="noopener noreferrer">Realtime</a></th>
    <th><a href="https://github.com/supabase/storage-api" target="_blank" rel="noopener noreferrer">Storage</a></th>
    <th>Functions</th>
  </tr>
  <!-- TEMPLATE FOR NEW ROW -->
  <!-- START ROW
  <tr>
    <td>lang</td>
    <td><a href="https://github.com/supabase-community/supabase-lang" target="_blank" rel="noopener noreferrer">supabase-lang</a></td>
    <td><a href="https://github.com/supabase-community/postgrest-lang" target="_blank" rel="noopener noreferrer">postgrest-lang</a></td>
    <td><a href="https://github.com/supabase-community/gotrue-lang" target="_blank" rel="noopener noreferrer">gotrue-lang</a></td>
    <td><a href="https://github.com/supabase-community/realtime-lang" target="_blank" rel="noopener noreferrer">realtime-lang</a></td>
    <td><a href="https://github.com/supabase-community/storage-lang" target="_blank" rel="noopener noreferrer">storage-lang</a></td>
  </tr>
  END ROW -->
  
  <th colspan="7">⚡️ Официален ⚡️</th>
  
  <tr>
    <td>JavaScript (TypeScript)</td>
    <td><a href="https://github.com/supabase/supabase-js" target="_blank" rel="noopener noreferrer">supabase-js</a></td>
    <td><a href="https://github.com/supabase/supabase-js/tree/master/packages/core/postgrest-js" target="_blank" rel="noopener noreferrer">postgrest-js</a></td>
    <td><a href="https://github.com/supabase/supabase-js/tree/master/packages/core/auth-js" target="_blank" rel="noopener noreferrer">auth-js</a></td>
    <td><a href="https://github.com/supabase/supabase-js/tree/master/packages/core/realtime-js" target="_blank" rel="noopener noreferrer">realtime-js</a></td>
    <td><a href="https://github.com/supabase/supabase-js/tree/master/packages/core/storage-js" target="_blank" rel="noopener noreferrer">storage-js</a></td>
    <td><a href="https://github.com/supabase/supabase-js/tree/master/packages/core/functions-js" target="_blank" rel="noopener noreferrer">functions-js</a></td>
  </tr>
    <tr>
    <td>Flutter</td>
    <td><a href="https://github.com/supabase/supabase-flutter" target="_blank" rel="noopener noreferrer">supabase-flutter</a></td>
    <td><a href="https://github.com/supabase/postgrest-dart" target="_blank" rel="noopener noreferrer">postgrest-dart</a></td>
    <td><a href="https://github.com/supabase/gotrue-dart" target="_blank" rel="noopener noreferrer">gotrue-dart</a></td>
    <td><a href="https://github.com/supabase/realtime-dart" target="_blank" rel="noopener noreferrer">realtime-dart</a></td>
    <td><a href="https://github.com/supabase/storage-dart" target="_blank" rel="noopener noreferrer">storage-dart</a></td>
    <td><a href="https://github.com/supabase/functions-dart" target="_blank" rel="noopener noreferrer">functions-dart</a></td>
  </tr>
  
  <th colspan="7">💚 Общност 💚</th>
  
  <tr>
    <td>C#</td>
    <td><a href="https://github.com/supabase-community/supabase-csharp" target="_blank" rel="noopener noreferrer">supabase-csharp</a></td>
    <td><a href="https://github.com/supabase-community/postgrest-csharp" target="_blank" rel="noopener noreferrer">postgrest-csharp</a></td>
    <td><a href="https://github.com/supabase-community/gotrue-csharp" target="_blank" rel="noopener noreferrer">gotrue-csharp</a></td>
    <td><a href="https://github.com/supabase-community/realtime-csharp" target="_blank" rel="noopener noreferrer">realtime-csharp</a></td>
    <td><a href="https://github.com/supabase-community/storage-csharp" target="_blank" rel="noopener noreferrer">storage-csharp</a></td>
    <td><a href="https://github.com/supabase-community/functions-csharp" target="_blank" rel="noopener noreferrer">functions-csharp</a></td>
  </tr>
  <tr>
    <td>Go</td>
    <td>-</td>
    <td><a href="https://github.com/supabase-community/postgrest-go" target="_blank" rel="noopener noreferrer">postgrest-go</a></td>
    <td><a href="https://github.com/supabase-community/gotrue-go" target="_blank" rel="noopener noreferrer">gotrue-go</a></td>
    <td>-</td>
    <td><a href="https://github.com/supabase-community/storage-go" target="_blank" rel="noopener noreferrer">storage-go</a></td>
    <td><a href="https://github.com/supabase-community/functions-go" target="_blank" rel="noopener noreferrer">functions-go</a></td>
  </tr>
  <tr>
    <td>Java</td>
    <td>-</td>
    <td>-</td>
    <td><a href="https://github.com/supabase-community/gotrue-java" target="_blank" rel="noopener noreferrer">gotrue-java</a></td>
    <td>-</td>
    <td><a href="https://github.com/supabase-community/storage-java" target="_blank" rel="noopener noreferrer">storage-java</a></td>
    <td>-</td>
  </tr>
  <tr>
    <td>Kotlin</td>
    <td><a href="https://github.com/supabase-community/supabase-kt" target="_blank" rel="noopener noreferrer">supabase-kt</a></td>
    <td><a href="https://github.com/supabase-community/supabase-kt/tree/master/Postgrest" target="_blank" rel="noopener noreferrer">postgrest-kt</a></td>
    <td><a href="https://github.com/supabase-community/supabase-kt/tree/master/GoTrue" target="_blank" rel="noopener noreferrer">gotrue-kt</a></td>
    <td><a href="https://github.com/supabase-community/supabase-kt/tree/master/Realtime" target="_blank" rel="noopener noreferrer">realtime-kt</a></td>
    <td><a href="https://github.com/supabase-community/supabase-kt/tree/master/Storage" target="_blank" rel="noopener noreferrer">storage-kt</a></td>
    <td><a href="https://github.com/supabase-community/supabase-kt/tree/master/Functions" target="_blank" rel="noopener noreferrer">functions-kt</a></td>
  </tr>
  <tr>
    <td>Python</td>
    <td><a href="https://github.com/supabase-community/supabase-py" target="_blank" rel="noopener noreferrer">supabase-py</a></td>
    <td><a href="https://github.com/supabase-community/postgrest-py" target="_blank" rel="noopener noreferrer">postgrest-py</a></td>
    <td><a href="https://github.com/supabase-community/gotrue-py" target="_blank" rel="noopener noreferrer">gotrue-py</a></td>
    <td><a href="https://github.com/supabase-community/realtime-py" target="_blank" rel="noopener noreferrer">realtime-py</a></td>
    <td><a href="https://github.com/supabase-community/storage-py" target="_blank" rel="noopener noreferrer">storage-py</a></td>
    <td><a href="https://github.com/supabase-community/functions-py" target="_blank" rel="noopener noreferrer">functions-py</a></td>
  </tr>
  <tr>
    <td>Ruby</td>
    <td><a href="https://github.com/supabase-community/supabase-rb" target="_blank" rel="noopener noreferrer">supabase-rb</a></td>
    <td><a href="https://github.com/supabase-community/postgrest-rb" target="_blank" rel="noopener noreferrer">postgrest-rb</a></td>
    <td>-</td>
    <td>-</td>
    <td>-</td>
    <td>-</td>
  </tr>
  <tr>
    <td>Rust</td>
    <td>-</td>
    <td><a href="https://github.com/supabase-community/postgrest-rs" target="_blank" rel="noopener noreferrer">postgrest-rs</a></td>
    <td>-</td>
    <td>-</td>
    <td>-</td>
    <td>-</td>
  </tr>
  <tr>
    <td>Swift</td>
    <td><a href="https://github.com/supabase-community/supabase-swift" target="_blank" rel="noopener noreferrer">supabase-swift</a></td>
    <td><a href="https://github.com/supabase-community/postgrest-swift" target="_blank" rel="noopener noreferrer">postgrest-swift</a></td>
    <td><a href="https://github.com/supabase-community/gotrue-swift" target="_blank" rel="noopener noreferrer">gotrue-swift</a></td>
    <td><a href="https://github.com/supabase-community/realtime-swift" target="_blank" rel="noopener noreferrer">realtime-swift</a></td>
    <td><a href="https://github.com/supabase-community/storage-swift" target="_blank" rel="noopener noreferrer">storage-swift</a></td>
    <td><a href="https://github.com/supabase-community/functions-swift" target="_blank" rel="noopener noreferrer">functions-swift</a></td>
  </tr>
  <tr>
    <td>Godot Engine (GDScript)</td>
    <td><a href="https://github.com/supabase-community/godot-engine.supabase" target="_blank" rel="noopener noreferrer">supabase-gdscript</a></td>
    <td><a href="https://github.com/supabase-community/postgrest-gdscript" target="_blank" rel="noopener noreferrer">postgrest-gdscript</a></td>
    <td><a href="https://github.com/supabase-community/gotrue-gdscript" target="_blank" rel="noopener noreferrer">gotrue-gdscript</a></td>
    <td><a href="https://github.com/supabase-community/realtime-gdscript" target="_blank" rel="noopener noreferrer">realtime-gdscript</a></td>
    <td><a href="https://github.com/supabase-community/storage-gdscript" target="_blank" rel="noopener noreferrer">storage-gdscript</a></td>
    <td><a href="https://github.com/supabase-community/functions-gdscript" target="_blank" rel="noopener noreferrer">functions-gdscript</a></td>
  </tr>
  
</table>

<!--- Remove this list if you're translating to another language, it's hard to keep updated across multiple files-->
<!--- Keep only the link to the list of translation files-->

## Преводи

- [арабски | العربية](/i18n/README.ar.md)
- [Албански / Shqip](/i18n/README.sq.md)
- [Бангла / বাংলা](/i18n/README.bn.md)
- [Български](/i18n/README.bg.md)
- [Каталонски / Català](/i18n/README.ca.md)
- [Датски / Dansk](/i18n/README.da.md)
- [Dutch / Nederlands](/i18n/README.nl.md)
- [Английски език](https://github.com/supabase/supabase)
- [Финландски / Suomalainen](/i18n/README.fi.md)
- [French / Français](/i18n/README.fr.md)
- [Немски / Deutsch](/i18n/README.de.md)
- [Гръцки / Ελληνικά](/i18n/README.gr.md)
- [Иврит / עברית](/i18n/README.he.md)
- [Hindi / हिंदी](/i18n/README.hi.md)
- [Унгарски / Magyar](/i18n/README.hu.md)
- [Непалски / नेपाली](/i18n/README.ne.md)
- [Индонезийски / Bahasa Indonesia](/i18n/README.id.md)
- [Италиански език / Italiano](/i18n/README.it.md)
- [Японски / 日本語](/i18n/README.jp.md)
- [Корейски / 한국어](/i18n/README.ko.md)
- [Малайски / Bahasa Malaysia](/i18n/README.ms.md)
- [Норвежки (Bokmål) / Norsk (Bokmål)](/i18n/README.nb-no.md)
- [Персийски език / فارسی](/i18n/README.fa.md)
- [Полски / Polski](/i18n/README.pl.md)
- [Portuguese / Português](/i18n/README.pt.md)
- [Португалски (бразилски) / Português Brasileiro](/i18n/README.pt-br.md)
- [Румънски език / Română](/i18n/README.ro.md)
- [Руски / Pусский](/i18n/README.ru.md)
- [Serbian / Srpski](/i18n/README.sr.md)
- [Sinhala / සිංහල](/i18n/README.si.md)
- [Spanish / Español](/i18n/README.es.md)
- [Опростен китайски език / 简体中文](/i18n/README.zh-cn.md)
- [Шведски език / Svenska](/i18n/README.sv.md)
- [Thai / ไทย](/i18n/README.th.md)
- [Традиционен китайски / 繁體中文](/i18n/README.zh-tw.md)
- [Турски език / Türkçe](/i18n/README.tr.md)
- [Украински / Українська](/i18n/README.uk.md)
- [Виетнамски / Tiếng Việt](/i18n/README.vi-vn.md)
- [Списък на преводите](/i18n/languages.md) <!--- Keep only this -->

---

## Спонсори

[![Нов спонсор](https://user-images.githubusercontent.com/10214025/90518111-e74bbb00-e198-11ea-8f88-c9e3c1aa4b5b.png)](https://github.com/sponsors/supabase)


## Links discovered
- [Supabase](https://supabase.com)
- [Документи](https://supabase.com/docs/guides/database)
- [Документи](https://supabase.com/docs/guides/auth)
- [Документи](https://supabase.com/docs/guides/api#rest-api-overview)
- [Документи](https://supabase.com/docs/guides/api#graphql-api-overview)
- [Документи](https://supabase.com/docs/guides/api#realtime-api-overview)
- [Docs](https://supabase.com/docs/guides/database/functions)
- [Docs](https://supabase.com/docs/guides/functions)
- [Документи](https://supabase.com/docs/guides/storage)
- [Supabase Dashboard](https://raw.githubusercontent.com/supabase/supabase/master/apps/www/public/images/github/supabase-dashboard.png)
- [supabase.com/docs](https://supabase.com/docs)
- [Getting Started](https://github.com/supabase/supabase/blob/master/DEVELOPERS.md)
- [Форум на общността](https://github.com/supabase/supabase/discussions)
- [GitHub Issues](https://github.com/supabase/supabase/issues)
- [Email Support](https://supabase.com/docs/support#business-support)
- [Discord](https://discord.supabase.com)
- [supabase.com/dashboard](https://supabase.com/dashboard)
- [[статус](https://supabase.com/docs/guides/getting-started/features#feature-status)
- [хоствана платформа](https://supabase.com/dashboard)
- [самостоятелно хоствате](https://supabase.com/docs/guides/hosting/overview)
- [да разработвате локално](https://supabase.com/docs/guides/local-development)
- [Архитектура](https://github.com/supabase/supabase/blob/master/apps/docs/public/img/supabase-architecture.svg)
- [PostgreSQL](https://www.postgresql.org/)
- [Realtime](https://github.com/supabase/realtime)
- [PostgREST](http://postgrest.org/)
- [pg_graphql](http://github.com/supabase/pg_graphql/)
- [Storage](https://github.com/supabase/storage-api)
- [postgres-meta](https://github.com/supabase/postgres-meta)
- [GoTrue](https://github.com/netlify/gotrue)
- [Kong](https://github.com/Kong/kong)
- [арабски | العربية](https://github.com/supabase/supabase/blob/master/i18n/README.ar.md)
- [Албански / Shqip](https://github.com/supabase/supabase/blob/master/i18n/README.sq.md)
- [Бангла / বাংলা](https://github.com/supabase/supabase/blob/master/i18n/README.bn.md)
- [Български](https://github.com/supabase/supabase/blob/master/i18n/README.bg.md)
- [Каталонски / Català](https://github.com/supabase/supabase/blob/master/i18n/README.ca.md)
- [Датски / Dansk](https://github.com/supabase/supabase/blob/master/i18n/README.da.md)
- [Dutch / Nederlands](https://github.com/supabase/supabase/blob/master/i18n/README.nl.md)
- [Английски език](https://github.com/supabase/supabase)
- [Финландски / Suomalainen](https://github.com/supabase/supabase/blob/master/i18n/README.fi.md)
- [French / Français](https://github.com/supabase/supabase/blob/master/i18n/README.fr.md)
- [Немски / Deutsch](https://github.com/supabase/supabase/blob/master/i18n/README.de.md)
- [Гръцки / Ελληνικά](https://github.com/supabase/supabase/blob/master/i18n/README.gr.md)
- [Иврит / עברית](https://github.com/supabase/supabase/blob/master/i18n/README.he.md)
- [Hindi / हिंदी](https://github.com/supabase/supabase/blob/master/i18n/README.hi.md)
- [Унгарски / Magyar](https://github.com/supabase/supabase/blob/master/i18n/README.hu.md)
- [Непалски / नेपाली](https://github.com/supabase/supabase/blob/master/i18n/README.ne.md)
- [Индонезийски / Bahasa Indonesia](https://github.com/supabase/supabase/blob/master/i18n/README.id.md)
- [Италиански език / Italiano](https://github.com/supabase/supabase/blob/master/i18n/README.it.md)
- [Японски / 日本語](https://github.com/supabase/supabase/blob/master/i18n/README.jp.md)
- [Корейски / 한국어](https://github.com/supabase/supabase/blob/master/i18n/README.ko.md)
- [Малайски / Bahasa Malaysia](https://github.com/supabase/supabase/blob/master/i18n/README.ms.md)
- [Норвежки (Bokmål) / Norsk (Bokmål)](https://github.com/supabase/supabase/blob/master/i18n/README.nb-no.md)
- [Персийски език / فارسی](https://github.com/supabase/supabase/blob/master/i18n/README.fa.md)
- [Полски / Polski](https://github.com/supabase/supabase/blob/master/i18n/README.pl.md)
- [Portuguese / Português](https://github.com/supabase/supabase/blob/master/i18n/README.pt.md)
- [Португалски (бразилски) / Português Brasileiro](https://github.com/supabase/supabase/blob/master/i18n/README.pt-br.md)
- [Румънски език / Română](https://github.com/supabase/supabase/blob/master/i18n/README.ro.md)
- [Руски / Pусский](https://github.com/supabase/supabase/blob/master/i18n/README.ru.md)
- [Serbian / Srpski](https://github.com/supabase/supabase/blob/master/i18n/README.sr.md)
- [Sinhala / සිංහල](https://github.com/supabase/supabase/blob/master/i18n/README.si.md)
- [Spanish / Español](https://github.com/supabase/supabase/blob/master/i18n/README.es.md)
- [Опростен китайски език / 简体中文](https://github.com/supabase/supabase/blob/master/i18n/README.zh-cn.md)
- [Шведски език / Svenska](https://github.com/supabase/supabase/blob/master/i18n/README.sv.md)
- [Thai / ไทย](https://github.com/supabase/supabase/blob/master/i18n/README.th.md)
- [Традиционен китайски / 繁體中文](https://github.com/supabase/supabase/blob/master/i18n/README.zh-tw.md)
- [Турски език / Türkçe](https://github.com/supabase/supabase/blob/master/i18n/README.tr.md)
- [Украински / Українська](https://github.com/supabase/supabase/blob/master/i18n/README.uk.md)
- [Виетнамски / Tiếng Việt](https://github.com/supabase/supabase/blob/master/i18n/README.vi-vn.md)
- [Списък на преводите](https://github.com/supabase/supabase/blob/master/i18n/languages.md)
- [![Нов спонсор](https://user-images.githubusercontent.com/10214025/90518111-e74bbb00-e198-11ea-8f88-c9e3c1aa4b5b.png)
- [PostgREST](https://github.com/postgrest/postgrest)
- [GoTrue](https://github.com/supabase/gotrue)
- [supabase-lang](https://github.com/supabase-community/supabase-lang)
- [postgrest-lang](https://github.com/supabase-community/postgrest-lang)
- [gotrue-lang](https://github.com/supabase-community/gotrue-lang)
- [realtime-lang](https://github.com/supabase-community/realtime-lang)
- [storage-lang](https://github.com/supabase-community/storage-lang)
- [supabase-js](https://github.com/supabase/supabase-js)
- [postgrest-js](https://github.com/supabase/supabase-js/tree/master/packages/core/postgrest-js)
- [auth-js](https://github.com/supabase/supabase-js/tree/master/packages/core/auth-js)
- [realtime-js](https://github.com/supabase/supabase-js/tree/master/packages/core/realtime-js)
- [storage-js](https://github.com/supabase/supabase-js/tree/master/packages/core/storage-js)
- [functions-js](https://github.com/supabase/supabase-js/tree/master/packages/core/functions-js)
- [supabase-flutter](https://github.com/supabase/supabase-flutter)
- [postgrest-dart](https://github.com/supabase/postgrest-dart)
- [gotrue-dart](https://github.com/supabase/gotrue-dart)
- [realtime-dart](https://github.com/supabase/realtime-dart)
- [storage-dart](https://github.com/supabase/storage-dart)
- [functions-dart](https://github.com/supabase/functions-dart)
- [supabase-csharp](https://github.com/supabase-community/supabase-csharp)
- [postgrest-csharp](https://github.com/supabase-community/postgrest-csharp)
- [gotrue-csharp](https://github.com/supabase-community/gotrue-csharp)
- [realtime-csharp](https://github.com/supabase-community/realtime-csharp)
- [storage-csharp](https://github.com/supabase-community/storage-csharp)
- [functions-csharp](https://github.com/supabase-community/functions-csharp)
- [postgrest-go](https://github.com/supabase-community/postgrest-go)
- [gotrue-go](https://github.com/supabase-community/gotrue-go)
- [storage-go](https://github.com/supabase-community/storage-go)
- [functions-go](https://github.com/supabase-community/functions-go)
- [gotrue-java](https://github.com/supabase-community/gotrue-java)

--- i18n/README.bn.md ---
<p align="center">
<img src="https://user-images.githubusercontent.com/8291514/213727234-cda046d6-28c6-491a-b284-b86c5cede25d.png#gh-light-mode-only">
<img src="https://user-images.githubusercontent.com/8291514/213727225-56186826-bee8-43b5-9b15-86e839d89393.png#gh-dark-mode-only">
</p>

---

# Supabase

[Supabase](https://supabase.com) একটি ওপেন সোর্স ফায়ারবেস বিকল্প। আমরা এন্টারপ্রাইজ-গ্রেড ওপেন সোর্স সরঞ্জাম ব্যবহার করে ফায়ারবেসের বৈশিষ্ট্যগুলি তৈরি করছি।

- [x] হোস্ট করা পোস্টগ্রেস ডাটাবেস. [ডক্স](https://supabase.com/docs/guides/database)
- [x] অথেনটিকেশন এবং অথরাইজড . [ডক্স](https://supabase.com/docs/guides/auth)
- [x] স্বয়ংক্রিয়ভাবে তৈরি এপিআই.
  - [x] রেস্ট. [ডক্স](https://supabase.com/docs/guides/api#rest-api-overview)
  - [x] রিয়েলটাইম সাবস্ক্রিপশন. [ডক্স](https://supabase.com/docs/guides/api#realtime-api-overview)
  - [x] গ্রাফকিউএল (বেটা). [ডক্স](https://supabase.com/docs/guides/api#graphql-api-overview)
- [x] ফাংশনস.
  - [x] ডাটাবেস ফাংশনস. [ডক্স](https://supabase.com/docs/guides/database/functions)
  - [x] এজ ফাংশনস. [ডক্স](https://supabase.com/docs/guides/functions)
- [x] ফাইল স্টোরেজ. [ডক্স](https://supabase.com/docs/guides/storage)
- [x] ড্যাশবোর্ড

![Supabase Dashboard](https://raw.githubusercontent.com/supabase/supabase/master/apps/www/public/images/github/supabase-dashboard.png)

## ডকুমেন্টেশন

সম্পূর্ণ ডকুমেন্টেশনের জন্য, দেখুন [supabase.com/docs](https://supabase.com/docs)

কিভাবে কন্ট্রিবিউট করতে হয় তা দেখতে, পরিদর্শন করুন [Getting Started](../DEVELOPERS.md)

## কমিউনিটি ও সাপোর্ট

- [কমিউনিটি ফোরাম](https://github.com/supabase/supabase/discussions)। সর্বোত্তম: তৈরি করতে সহায়তা, ডাটাবেস সেরা অনুশীলন সম্পর্কে আলোচনা।
- [গিটহাব ইস্যু](https://github.com/supabase/supabase/issues)। সর্বোত্তম: সুপাবেস ব্যবহার করতে আপনি যে বাগ এবং ত্রুটির সম্মুখীন হন।
- [ইমেইল সাপোর্ট](https://supabase.com/docs/support#business-support)। সর্বোত্তম: আপনার ডাটাবেস বা অবকাঠামো নিয়ে সমস্যা।
- [ডিসকোর্ড](https://discord.supabase.com)। সর্বোত্তম: আপনার অ্যাপ্লিকেশনগুলি শেয়ার করা এবং কমিউনিটির সাথে দেখা সাক্ষাৎ করা৷

## স্ট্যাটাস

- [x] আলফা: আমরা কাছের গ্রাহকদের সাথে Supabase পরীক্ষা করছি
- [x] পাবলিক আলফা: যে কেউ [supabase.com/dashboard](https://supabase.com/dashboard) এ সাইন আপ করতে পারেন। কিন্তু আমাদের উপর সহজ যান, কয়েক kinks আছে
- [x] পাবলিক বেটা: বেশিরভাগ নন-এন্টারপ্রাইজ ব্যবহারের ক্ষেত্রে যথেষ্ট স্থিতিশীল
- [ ] পাবলিক: প্রোডাকশন রেডি

আমরা বর্তমানে পাবলিক বিটাতে আছি। প্রধান আপডেটের বিষয়ে অবহিত হওয়ার জন্য এই রেপোর "রিলিজ" দেখুন।

<kbd><img src="https://raw.githubusercontent.com/supabase/supabase/d5f7f413ab356dc1a92075cb3cee4e40a957d5b1/web/static/watch-repo.gif" alt="এই রিপু দেখুন"/></kbd>

---

## কিভাবে এটা কাজ করে

Supabase হল ওপেন সোর্স টুলের সংমিশ্রণ। আমরা এন্টারপ্রাইজ-গ্রেড, ওপেন সোর্স পণ্য ব্যবহার করে ফায়ারবেসের বৈশিষ্ট্যগুলি তৈরি করছি। যদি সরঞ্জাম এবং সম্প্রদায়গুলি বিদ্যমান হয়, MIT, Apache 2, বা সমতুল্য ওপেন সোর্স লাইসেন্সের সাথে, আমরা সেই সরঞ্জামটি ব্যবহার করব এবং সমর্থন করব। যদি সরঞ্জামটি বিদ্যমান না হয়, আমরা এটি নিজেরাই তৈরি করবো।

**স্থাপত্য**

সুপাবেস হল একটি [হোস্ট করা প্ল্যাটফর্ম](https://supabase.com/dashboard)। আপনি সাইন আপ করে এবং কিছু ইনস্টল না করে সুপাবেস ব্যবহার শুরু করতে পারেন।
এছাড়াও আপনি [স্ব-হোস্ট](https://supabase.com/docs/guides/hosting/overview) এবং [ডেভেলপ লোকালি](https://supabase.com/docs/guides/local-development) করতে পারেন।

![আর্কিটেকচার](https://github.com/supabase/supabase/blob/master/apps/docs/public/img/supabase-architecture.svg)

- [PostgreSQL](https://www.postgresql.org/) হল একটি অবজেক্ট-রিলেশনাল ডাটাবেস সিস্টেম যার 30 বছরের বেশি সক্রিয় বিকাশ রয়েছে যা এটিকে নির্ভরযোগ্যতা, বৈশিষ্ট্যের দৃঢ়তা এবং কর্মক্ষমতার জন্য একটি শক্তিশালী খ্যাতি অর্জন করেছে।
- [রিয়েলটাইম](https://github.com/supabase/realtime) হল একটি Elixir সার্ভার যা আপনাকে ওয়েবসকেট ব্যবহার করে PostgreSQL সন্নিবেশ, আপডেট এবং মুছে ফেলা শুনতে দেয়। ডাটাবেস পরিবর্তনের জন্য রিয়েলটাইম পোল পোস্টগ্রেসের অন্তর্নির্মিত প্রতিলিপি কার্যকারিতা, পরিবর্তনগুলিকে JSON-এ রূপান্তরিত করে, তারপর অনুমোদিত ক্লায়েন্টদের কাছে ওয়েবসকেটের মাধ্যমে JSON সম্প্রচার করে।
- [PostgREST](http://postgrest.org/) একটি ওয়েব সার্ভার যা আপনার PostgreSQL ডাটাবেসকে সরাসরি একটি রেস্টফুল এপিআইতে পরিণত করে।
- [স্টোরেজ](https://github.com/supabase/storage-api) অনুমতিগুলি পরিচালনা করতে পোস্টগ্রেস ব্যবহার করে S3-এ সঞ্চিত ফাইলগুলি পরিচালনা করার জন্য একটি রেস্টফুল ইন্টারফেস প্রদান করে।
- [পোস্টগ্রেস-মেটা](https://github.com/supabase/postgres-meta) হল আপনার পোস্টগ্রেস পরিচালনা করার জন্য একটি রেস্টফুল এপিআই, যা আপনাকে টেবিল আনতে, ভূমিকা যোগ করতে এবং কোয়েরি চালানোর অনুমতি দেয়।
- [গোট্রু](https://github.com/netlify/gotrue) ব্যবহারকারীদের পরিচালনা এবং SWT টোকেন ইস্যু করার জন্য একটি SWT ভিত্তিক এপিআই
- [কং](https://github.com/Kong/kong) হল একটি ক্লাউড-নেটিভ এপিআই গেটওয়ে।

#### ক্লায়েন্ট লাইব্রেরি

ক্লায়েন্ট লাইব্রেরির জন্য আমাদের পদ্ধতি মডুলার। প্রতিটি উপ-লাইব্রেরি একটি একক বহিরাগত সিস্টেমের জন্য একটি স্বতন্ত্র বাস্তবায়ন। এটি আমাদের উপায় গুলোর মধ্যে একটি যেভাবে বিদ্যমান সরঞ্জামগুলিকে সমর্থন করি৷

<table style="table-layout:fixed; white-space: nowrap;">৷
  <tr>
    <th>ভাষা</th>
    <th>ক্লায়েন্ট</th>
    <th colspan="4">ফিচার-ক্লায়েন্ট (সুপাবেস ক্লায়েন্টে বান্ডিল)</th>
  </tr>
  <tr>
    <th></th>
    <th>Supabase</th>
    <th><a href="https://github.com/postgrest/postgrest" target="_blank" rel="noopener noreferrer">PostgREST</a></th>
    <th><a href="https://github.com/supabase/gotrue" target="_blank" rel="noopener noreferrer">গোট্রু</a></th>
    <th><a href="https://github.com/supabase/realtime" target="_blank" rel="noopener noreferrer">রিয়েলটাইম</a></th>
    <th><a href="https://github.com/supabase/storage-api" target="_blank" rel="noopener noreferrer">স্টোরেজ</a></th>
  </tr>
  <!-- নতুন সারির জন্য টেমপ্লেট -->
  <!-- সারি শুরু করুন
  <tr>
    <td>lang</td>
    <td><a href="https://github.com/supabase-community/supabase-lang" target="_blank" rel="noopener noreferrer">supabase-lang</a></td>
    <td><a href="https://github.com/supabase-community/postgrest-lang" target="_blank" rel="noopener noreferrer">postgrest-lang</a></td>
    <td><a href="https://github.com/supabase-community/gotrue-lang" target="_blank" rel="noopener noreferrer">gotrue-lang</a></td>
    <td><a href="https://github.com/supabase-community/realtime-lang" target="_blank" rel="noopener noreferrer">রিয়েলটাইম-ল্যাং</a></td>
    <td><a href="https://github.com/supabase-community/storage-lang" target="_blank" rel="noopener noreferrer">স্টোরেজ-ল্যাং</a></td>
  </tr>
  শেষ সারি -->
  <th colspan="6">⚡️ অফিসিয়াল ⚡️</th>
  <tr>
    <td>জাভাস্ক্রিপ্ট (টাইপস্ক্রিপ্ট)</td>
    <td><a href="https://github.com/supabase/supabase-js" target="_blank" rel="noopener noreferrer">সুপাবেস-জেএস</a></td>
    <td><a href="https://github.com/supabase/supabase-js/tree/master/packages/core/postgrest-js" target="_blank" rel="noopener noreferrer">postgrest-js</a></td>
    <td><a href="https://github.com/supabase/supabase-js/tree/master/packages/core/auth-js" target="_blank" rel="noopener noreferrer">গোট্রু-জেএস</a></td>
    <td><a href="https://github.com/supabase/supabase-js/tree/master/packages/core/realtime-js" target="_blank" rel="noopener noreferrer">রিয়েলটাইম-জেএস</a></td>
    <td><a href="https://github.com/supabase/supabase-js/tree/master/packages/core/storage-js" target="_blank" rel="noopener noreferrer">স্টোরেজ-জেএস</a></td>
  </tr>
  <th colspan="6">💚 সম্প্রদায় 💚</th>
  <tr>
    <td>C#</td>
    <td><a href="https://github.com/supabase-community/supabase-csharp" target="_blank" rel="noopener noreferrer">supabase-csharp</a></td>
    <td><a href="https://github.com/supabase-community/postgrest-csharp" target="_blank" rel="noopener noreferrer">postgrest-csharp</a></td>
    <td><a href="https://github.com/supabase-community/gotrue-csharp" target="_blank" rel="noopener noreferrer">গোট্রু-csharp</a></td>
    <td><a href="https://github.com/supabase-community/realtime-csharp" target="_blank" rel="noopener noreferrer">রিয়েলটাইম-csharp</a></td>
    <td>-</td>
  </tr>
  <tr>
    <td>ডার্ট (ফ্লাটার)</td>
    <td><a href="https://github.com/supabase/supabase-Flutter" target="_blank" rel="noopener noreferrer">সুপাবেস-ডার্ট</a></td>
    <td><a href="https://github.com/supabase/postgrest-dart" target="_blank" rel="noopener noreferrer">postgrest-ডার্ট</a></td>
    <td><a href="https://github.com/supabase/gotrue-dart" target="_blank" rel="noopener noreferrer">গোট্রু-ডার্ট</a></td>
    <td><a href="https://github.com/supabase/realtime-dart" target="_blank" rel="noopener noreferrer">রিয়েলটাইম-ডার্ট</a></td>
    <td><a href="https://github.com/supabase/storage-dart" target="_blank" rel="noopener noreferrer">স্টোরেজ-ডার্ট</a></td>
  </tr>
  <tr>
    <td>গো</td>
    <td>-</td>
    <td><a href="https://github.com/supabase-community/postgrest-go" target="_blank" rel="noopener noreferrer">postgrest-go</a></td>
    <td>-</td>
    <td>-</td>
    <td>-</td>
  </tr>
  <tr>
    <td>জাভা</td>
    <td>-</td>
    <td>-</td>
    <td><a href="https://github.com/supabase-community/gotrue-java" target="_blank" rel="noopener noreferrer">গোট্রু-জাভা</a></td>
    <td>-</td>
    <td>-</td>
  </tr>
  <tr>
    <td>কোটলিন</td>
    <td><a href="https://github.com/supabase-community/supabase-kt" target="_blank" rel="noopener noreferrer">supabase-kt</a></td>
    <td><a href="https://github.com/supabase-community/supabase-kt/tree/master/Postgrest" target="_blank" rel="noopener noreferrer">postgrest-kt</a></td>
    <td><a href="https://github.com/supabase-community/supabase-kt/tree/master/GoTrue" target="_blank" rel="noopener noreferrer">gotrue-kt</a></td>
    <td><a href="https://github.com/supabase-community/supabase-kt/tree/master/Realtime" target="_blank" rel="noopener noreferrer">realtime-kt</a></td>
    <td><a href="https://github.com/supabase-community/supabase-kt/tree/master/Storage" target="_blank" rel="noopener noreferrer">storage-kt</a></td>
  </tr>
  <tr>
    <td>পাইথন</td>
    <td><a href="https://github.com/supabase-community/supabase-py" target="_blank" rel="noopener noreferrer">supabase-py</a></td>
    <td><a href="https://github.com/supabase-community/postgrest-py" target="_blank" rel="noopener noreferrer">postgrest-py</a></td>
    <td><a href="https://github.com/supabase-community/gotrue-py" target="_blank" rel="noopener noreferrer">গোট্রু-py</a></td>
    <td><a href="https://github.com/supabase-community/realtime-py" target="_blank" rel="noopener noreferrer">রিয়েলটাইম-py</a></td>
    <td>-</td>
  </tr>
  <tr>
    <td>রুবি</td>
    <td><a href="https://github.com/supabase-community/supabase-rb" target="_blank" rel="noopener noreferrer">সুপাবেস-rb</a></td>
    <td><a href="https://github.com/supabase-community/postgrest-rb" target="_blank" rel="noopener noreferrer">postgrest-rb</a></td>
    <td>-</td>
    <td>-</td>
    <td>-</td>
  </tr>
  <tr>
    <td>রাস্ট</td>
    <td>-</td>
    <td><a href="https://github.com/supabase-community/postgrest-rs" target="_blank" rel="noopener noreferrer">postgrest-rs</a></td>
    <td>-</td>
    <td>-</td>
    <td>-</td>
  </tr>
  <tr>
    <td>সুইফট</td>
    <td><a href="https://github.com/supabase-community/supabase-swift" target="_blank" rel="noopener noreferrer">সুপাবেস-সুইফট</a></td>
    <td><a href="https://github.com/supabase-community/postgrest-swift" target="_blank" rel="noopener noreferrer">postgrest-সুইফট</a></td>
    <td><a href="https://github.com/supabase-community/gotrue-swift" target="_blank" rel="noopener noreferrer">গোট্রু-সুইফট</a></td>
    <td><a href="https://github.com/supabase-community/realtime-swift" target="_blank" rel="noopener noreferrer">রিয়েলটাইম-সুইফট</a></td>
    <td><a href="https://github.com/supabase-community/storage-swift" target="_blank" rel="noopener noreferrer">স্টোরেজ-সুইফট</a></td>
  </tr>
</table>

## অনুবাদ

- [অনুবাদের তালিকা](/i18n/languages.md) <!--- Keep only this -->

## পৃষ্ঠপোষক

[![নতুন পৃষ্ঠপোষক](https://user-images.githubusercontent.com/10214025/90518111-e74bbb00-e198-11ea-8f88-c9e3c1aa4b5b.png)](https://github.com/sponsors/supabase)


## Links discovered
- [Supabase](https://supabase.com)
- [ডক্স](https://supabase.com/docs/guides/database)
- [ডক্স](https://supabase.com/docs/guides/auth)
- [ডক্স](https://supabase.com/docs/guides/api#rest-api-overview)
- [ডক্স](https://supabase.com/docs/guides/api#realtime-api-overview)
- [ডক্স](https://supabase.com/docs/guides/api#graphql-api-overview)
- [ডক্স](https://supabase.com/docs/guides/database/functions)
- [ডক্স](https://supabase.com/docs/guides/functions)
- [ডক্স](https://supabase.com/docs/guides/storage)
- [Supabase Dashboard](https://raw.githubusercontent.com/supabase/supabase/master/apps/www/public/images/github/supabase-dashboard.png)
- [supabase.com/docs](https://supabase.com/docs)
- [Getting Started](https://github.com/supabase/supabase/blob/master/DEVELOPERS.md)
- [কমিউনিটি ফোরাম](https://github.com/supabase/supabase/discussions)
- [গিটহাব ইস্যু](https://github.com/supabase/supabase/issues)
- [ইমেইল সাপোর্ট](https://supabase.com/docs/support#business-support)
- [ডিসকোর্ড](https://discord.supabase.com)
- [supabase.com/dashboard](https://supabase.com/dashboard)
- [হোস্ট করা প্ল্যাটফর্ম](https://supabase.com/dashboard)
- [স্ব-হোস্ট](https://supabase.com/docs/guides/hosting/overview)
- [ডেভেলপ লোকালি](https://supabase.com/docs/guides/local-development)
- [আর্কিটেকচার](https://github.com/supabase/supabase/blob/master/apps/docs/public/img/supabase-architecture.svg)
- [PostgreSQL](https://www.postgresql.org/)
- [রিয়েলটাইম](https://github.com/supabase/realtime)
- [PostgREST](http://postgrest.org/)
- [স্টোরেজ](https://github.com/supabase/storage-api)
- [পোস্টগ্রেস-মেটা](https://github.com/supabase/postgres-meta)
- [গোট্রু](https://github.com/netlify/gotrue)
- [কং](https://github.com/Kong/kong)
- [অনুবাদের তালিকা](https://github.com/supabase/supabase/blob/master/i18n/languages.md)
- [![নতুন পৃষ্ঠপোষক](https://user-images.githubusercontent.com/10214025/90518111-e74bbb00-e198-11ea-8f88-c9e3c1aa4b5b.png)
- [PostgREST](https://github.com/postgrest/postgrest)
- [গোট্রু](https://github.com/supabase/gotrue)
- [supabase-lang](https://github.com/supabase-community/supabase-lang)
- [postgrest-lang](https://github.com/supabase-community/postgrest-lang)
- [gotrue-lang](https://github.com/supabase-community/gotrue-lang)
- [রিয়েলটাইম-ল্যাং](https://github.com/supabase-community/realtime-lang)
- [স্টোরেজ-ল্যাং](https://github.com/supabase-community/storage-lang)
- [সুপাবেস-জেএস](https://github.com/supabase/supabase-js)
- [postgrest-js](https://github.com/supabase/supabase-js/tree/master/packages/core/postgrest-js)
- [গোট্রু-জেএস](https://github.com/supabase/supabase-js/tree/master/packages/core/auth-js)
- [রিয়েলটাইম-জেএস](https://github.com/supabase/supabase-js/tree/master/packages/core/realtime-js)
- [স্টোরেজ-জেএস](https://github.com/supabase/supabase-js/tree/master/packages/core/storage-js)
- [supabase-csharp](https://github.com/supabase-community/supabase-csharp)
- [postgrest-csharp](https://github.com/supabase-community/postgrest-csharp)
- [গোট্রু-csharp](https://github.com/supabase-community/gotrue-csharp)
- [রিয়েলটাইম-csharp](https://github.com/supabase-community/realtime-csharp)
- [সুপাবেস-ডার্ট](https://github.com/supabase/supabase-Flutter)
- [postgrest-ডার্ট](https://github.com/supabase/postgrest-dart)
- [গোট্রু-ডার্ট](https://github.com/supabase/gotrue-dart)
- [রিয়েলটাইম-ডার্ট](https://github.com/supabase/realtime-dart)
- [স্টোরেজ-ডার্ট](https://github.com/supabase/storage-dart)
- [postgrest-go](https://github.com/supabase-community/postgrest-go)
- [গোট্রু-জাভা](https://github.com/supabase-community/gotrue-java)
- [supabase-kt](https://github.com/supabase-community/supabase-kt)
- [postgrest-kt](https://github.com/supabase-community/supabase-kt/tree/master/Postgrest)
- [gotrue-kt](https://github.com/supabase-community/supabase-kt/tree/master/GoTrue)
- [realtime-kt](https://github.com/supabase-community/supabase-kt/tree/master/Realtime)
- [storage-kt](https://github.com/supabase-community/supabase-kt/tree/master/Storage)
- [supabase-py](https://github.com/supabase-community/supabase-py)
- [postgrest-py](https://github.com/supabase-community/postgrest-py)
- [গোট্রু-py](https://github.com/supabase-community/gotrue-py)
- [রিয়েলটাইম-py](https://github.com/supabase-community/realtime-py)
- [সুপাবেস-rb](https://github.com/supabase-community/supabase-rb)
- [postgrest-rb](https://github.com/supabase-community/postgrest-rb)
- [postgrest-rs](https://github.com/supabase-community/postgrest-rs)
- [সুপাবেস-সুইফট](https://github.com/supabase-community/supabase-swift)
- [postgrest-সুইফট](https://github.com/supabase-community/postgrest-swift)
- [গোট্রু-সুইফট](https://github.com/supabase-community/gotrue-swift)
- [রিয়েলটাইম-সুইফট](https://github.com/supabase-community/realtime-swift)
- [স্টোরেজ-সুইফট](https://github.com/supabase-community/storage-swift)

--- i18n/README.ca.md ---
<p align="center">
<img src="https://user-images.githubusercontent.com/8291514/213727234-cda046d6-28c6-491a-b284-b86c5cede25d.png#gh-light-mode-only">
<img src="https://user-images.githubusercontent.com/8291514/213727225-56186826-bee8-43b5-9b15-86e839d89393.png#gh-dark-mode-only">
</p>

---

# Supabase

[Supabase](https://supabase.com) és una alternativa de codi obert a Firebase. Estem construint les funcionalitats de Firebase usant eines de codi obert de nivell empresarial.

- [x] Allotjament de base de dades Postgres
- [x] Subscripcions en temps real
- [x] Autenticació i autorització
- [x] API autogenerada
- [x] Panell de control
- [x] Emmagatzematge
- [x] Funcions

![Supabase Dashboard](https://raw.githubusercontent.com/supabase/supabase/master/apps/www/public/images/github/supabase-dashboard.png)

## Documentació

Per a veure la documentació completa, visita [supabase.com/docs](https://supabase.com/docs).

## Comunitat i suport

- [Fòrum de la comunitat](https://github.com/supabase/supabase/discussions). Millor per a: ajuda construint, discussions sobre les millors pràctiques de base de dades.
- [GitHub Issues](https://github.com/supabase/supabase/issues). Millor per a: errors que et pots trobar utilitzant Supabase.
- [Suport per correu electrònic](https://supabase.com/docs/support#business-*support). Millor per a: problemes amb la base de dades o infraestructura.
- [Discord](https://discord.supabase.com). Millor per a: compartir les teves aplicacions i passar l’estona amb la comunitat.

## Estat

- [x] Alfa: Estem provant Supabase amb un cercle tancat de clients.
- [x] Alfa pública: Qualsevol pot registrar-se a [supabase.com/dashboard](https://supabase.com/dashboard). Però sigues flexible amb nosaltres; encara poden existir obstacles.
- [x] Beta pública: Prou estable per a la majoria dels casos no empresarials.
- [ ] Públic: Llest per a producció.

Actualment estem en la fase de beta pública. Pots subscriure’t a les _releases_ d’aquest repositori per a mantenir-te notificat d’actualitzacions majors.

<kbd><img src="https://raw.githubusercontent.com/supabase/supabase/d5f7f413ab356dc1a92075cb3cee4e40a957d5b1/web/static/watch-repo.gif" alt="Segueix aquest repositori"/></kbd>

---

## Com funciona

Supabase és una combinació d’eines de codi obert. Estem construint les funcionalitats de Firebase utilitzant solucions de codi obert de nivell empresarial. Si les eines i comunitats existeixen amb una llicència oberta MIT, Apache 2 o equivalent, usarem i secundarem tal eina. Si l’eina no existeix, la desenvoluparem i la llançarem com a eina de codi obert nosaltres mateixos. Supabase no és un mapatge _1 a 1_ de Firebase. El nostre objectiu és donar als desenvolupadors una experiència semblant a la de Firebase utilitzant eines de codi obert.

**Arquitectura actual**

Supabase és una [plataforma allotjada](https://supabase.com/dashboard). Et pots registrar i començar a utilitzar Supabase sense instal·lar res. També podeu tenir una [_host_ pròpia](https://supabase.com/docs/guides/hosting/overview) i [desenvolupar localment](https://supabase.com/docs/guides/local-development).

![Arquitectura](https://github.com/supabase/supabase/blob/master/apps/docs/public/img/supabase-architecture.svg)

- [PostgreSQL](https://www.postgresql.org/) és un sistema de base de dades objecte–relacional amb més de 30 anys de desenvolupament actiu que s’ha guanyat la seva forta reputació per ser de confiança, robust i d’alt rendiment.
- [Temps real](https://github.com/supabase/realtime) és un server construït en Elixir que permet escoltar els _inserts_, _updates_ i _deletes_ de PostgreSQL utilitzant WebSockets. Supabase escolta a la funcionalitat de replicació integrada de PostgreSQL, converteix el byte de replicació en un JSON i després transmet el JSON a través de WebSockets.
- [PostgREST](http://postgrest.org/) és un servidor web que converteix la base de dades PostgreSQL directament en una API RESTful.
- [Emmagatzematge](https://github.com/supabase/storage-api) proporciona una interfície RESTful per a manipular els arxius allotjats en S3, utilitzant Postgres per a gestionar els permisos.
- [postgres-meta](https://github.com/supabase/postgres-meta) és una API RESTful per a gestionar Postgres, permet obtenir informació de taules, agregar rols, executar consultes, etc.
- [GoTrue](https://github.com/netlify/gotrue) és una API basada en SWT per a administrar usuaris i distribuir tokens SWT.
- [Kong](https://github.com/kong/kong) és un API gateway nadiu allotjat en el núvol.

#### Llibreries de client

La nostra llibreria de client és modular. Cada subllibreria és una implementació independent per a cada sistema extern. Aquesta és una de les maneres de donar suport a les eines existents.

- **`supabase-{lang}`**: Combina llibreries i afegeix millores.
  - `postgrest-{lang}`: Llibreria de client per a treballar amb [PostgREST](https://github.com/postgrest/postgrest)
  - `realtime-{lang}`: Llibreria de client per a treballar amb [Realtime](https://github.com/supabase/realtime)
  - `gotrue-{lang}`: Llibreria de client per a treballar amb [GoTrue](https://github.com/netlify/gotrue)

| Repositori            | Oficial                                                                                | Comunitat                                                                                                                                                                                                                  |
| --------------------- | -------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`supabase-{lang}`** | [`JS`](https://github.com/supabase/supabase-js)                                        | [`C#`](https://github.com/supabase/supabase-csharp) \| [`Flutter`](https://github.com/supabase/supabase-Flutter) \| [`Python`](https://github.com/supabase/supabase-py) \| `Rust`                                          |
| `postgrest-{lang}`    | [`JS`](https://github.com/supabase/supabase-js/tree/master/packages/core/postgrest-js) | [`C#`](https://github.com/supabase/postgrest-csharp) \| [`Dart`](https://github.com/supabase/postgrest-dart) \| [`Python`](https://github.com/supabase/postgrest-py) \| [`Rust`](https://github.com/supabase/postgrest-rs) |
| `realtime-{lang}`     | [`JS`](https://github.com/supabase/supabase-js/tree/master/packages/core/realtime-js)  | [`C#`](https://github.com/supabase/realtime-csharp) \| [`Dart`](https://github.com/supabase/realtime-dart) \| [`Python`](https://github.com/supabase/realtime-py) \| `Rust`                                                |
| `gotrue-{lang}`       | [`JS`](https://github.com/supabase/supabase-js/tree/master/packages/core/auth-js)      | [`C#`](https://github.com/supabase/gotrue-csharp) \| [`Dart`](https://github.com/supabase/gotrue-dart) \| [`Python`](https://github.com/supabase/gotrue-py) \| `Rust`                                                      |

<!--- Remove this list if you're traslating to another language, it's hard to keep updated across multiple files-->
<!--- Keep only the link to the list of translation files-->

## Traduccions

- [Llista de traduccions](/i18n/languages.md) <!--- Keep only the this-->

---

## Patrocinadors

[![Nou patrocinador](https://user-images.githubusercontent.com/10214025/90518111-e74bbb00-e198-11ea-8f88-c9e3c1aa4b5b.png)](https://github.com/sponsors/supabase)


## Links discovered
- [Supabase](https://supabase.com)
- [Supabase Dashboard](https://raw.githubusercontent.com/supabase/supabase/master/apps/www/public/images/github/supabase-dashboard.png)
- [supabase.com/docs](https://supabase.com/docs)
- [Fòrum de la comunitat](https://github.com/supabase/supabase/discussions)
- [GitHub Issues](https://github.com/supabase/supabase/issues)
- [Suport per correu electrònic](https://supabase.com/docs/support#business-*support)
- [Discord](https://discord.supabase.com)
- [supabase.com/dashboard](https://supabase.com/dashboard)
- [plataforma allotjada](https://supabase.com/dashboard)
- [_host_ pròpia](https://supabase.com/docs/guides/hosting/overview)
- [desenvolupar localment](https://supabase.com/docs/guides/local-development)
- [Arquitectura](https://github.com/supabase/supabase/blob/master/apps/docs/public/img/supabase-architecture.svg)
- [PostgreSQL](https://www.postgresql.org/)
- [Temps real](https://github.com/supabase/realtime)
- [PostgREST](http://postgrest.org/)
- [Emmagatzematge](https://github.com/supabase/storage-api)
- [postgres-meta](https://github.com/supabase/postgres-meta)
- [GoTrue](https://github.com/netlify/gotrue)
- [Kong](https://github.com/kong/kong)
- [PostgREST](https://github.com/postgrest/postgrest)
- [Realtime](https://github.com/supabase/realtime)
- [`JS`](https://github.com/supabase/supabase-js)
- [`C#`](https://github.com/supabase/supabase-csharp)
- [`Flutter`](https://github.com/supabase/supabase-Flutter)
- [`Python`](https://github.com/supabase/supabase-py)
- [`JS`](https://github.com/supabase/supabase-js/tree/master/packages/core/postgrest-js)
- [`C#`](https://github.com/supabase/postgrest-csharp)
- [`Dart`](https://github.com/supabase/postgrest-dart)
- [`Python`](https://github.com/supabase/postgrest-py)
- [`Rust`](https://github.com/supabase/postgrest-rs)
- [`JS`](https://github.com/supabase/supabase-js/tree/master/packages/core/realtime-js)
- [`C#`](https://github.com/supabase/realtime-csharp)
- [`Dart`](https://github.com/supabase/realtime-dart)
- [`Python`](https://github.com/supabase/realtime-py)
- [`JS`](https://github.com/supabase/supabase-js/tree/master/packages/core/auth-js)
- [`C#`](https://github.com/supabase/gotrue-csharp)
- [`Dart`](https://github.com/supabase/gotrue-dart)
- [`Python`](https://github.com/supabase/gotrue-py)
- [Llista de traduccions](https://github.com/supabase/supabase/blob/master/i18n/languages.md)
- [![Nou patrocinador](https://user-images.githubusercontent.com/10214025/90518111-e74bbb00-e198-11ea-8f88-c9e3c1aa4b5b.png)

--- i18n/README.cs.md ---
<p align="center">
<img src="https://user-images.githubusercontent.com/8291514/213727234-cda046d6-28c6-491a-b284-b86c5cede25d.png#gh-light-mode-only">
<img src="https://user-images.githubusercontent.com/8291514/213727225-56186826-bee8-43b5-9b15-86e839d89393.png#gh-dark-mode-only">
</p>

---

# Supabase

[Supabase](https://supabase.com) je open source alternativa Firebase. Vytváříme funkce Firebase pomocí open source nástrojů podnikové třídy.

- [x] hostovaná databáze Postgres. [Dokumenty](https://supabase.com/docs/guides/database)
- [x] Ověřování a autorizace. [Dokumenty](https://supabase.com/docs/guides/auth)
- [x] Automaticky generované rozhraní API.
  - [x] REST. [Dokumenty](https://supabase.com/docs/guides/api#rest-api-overview)
  - [x] GraphQL. [Dokumenty](https://supabase.com/docs/guides/api#graphql-api-overview)
  - [x] Odběry v reálném čase. [Dokumenty](https://supabase.com/docs/guides/api#realtime-api-overview)
- [x] Funkce.
  - [x] Databázové funkce. [Docs](https://supabase.com/docs/guides/database/functions)
  - [x] Okrajové funkce [Docs](https://supabase.com/docs/guides/functions)
- [x] Ukládání souborů. [Dokumenty](https://supabase.com/docs/guides/storage)
- [x] Přístrojový panel

![Supabase Dashboard](https://raw.githubusercontent.com/supabase/supabase/master/apps/www/public/images/github/supabase-dashboard.png)

## Dokumentace

Úplnou dokumentaci naleznete na adrese [supabase.com/docs](https://supabase.com/docs)

Chcete-li zjistit, jak přispívat, navštivte stránku [Začínáme](../DEVELOPERS.md)

## Komunita a podpora

- [Fórum komunity](https://github.com/supabase/supabase/discussions). Nejlépe pro: pomoc při vytváření, diskuse o osvědčených postupech při práci s databází.
- [GitHub Issues](https://github.com/supabase/supabase/issues). Nejlépe pro: chyby a omyly, na které narazíte při používání databáze Supabase.
- [E-mailová podpora](https://supabase.com/docs/support#business-support). Nejlepší pro: problémy s vaší databází nebo infrastrukturou.
- [Discord](https://discord.supabase.com). Nejlepší pro: sdílení vašich aplikací a setkávání s komunitou.

## Stav

- [x] Alfa: Testujeme Supabase s uzavřenou skupinou zákazníků
- [x] Veřejná alfa: [supabase.com/dashboard](https://supabase.com/dashboard). Ale buďte na nás mírní, je tu několik zádrhelů
- [x] Veřejná beta verze: Dostatečně stabilní pro většinu případů použití mimo podniky
- [ ] Veřejná: Všeobecná dostupnost [[status](https://supabase.com/docs/guides/getting-started/features#feature-status)]

V současné době jsme ve fázi Public Beta. Sledujte "releases" tohoto repozitáře, abyste byli upozorněni na hlavní aktualizace.

<kbd><img src="https://raw.githubusercontent.com/supabase/supabase/d5f7f413ab356dc1a92075cb3cee4e40a957d5b1/web/static/watch-repo.gif" alt="Watch this repo"/></kbd>

---

## Jak to funguje

Supabase je kombinací nástrojů s otevřeným zdrojovým kódem. Funkce Firebase vytváříme pomocí open source produktů podnikové třídy. Pokud existují nástroje a komunity s otevřenou licencí MIT, Apache 2 nebo ekvivalentní, budeme tento nástroj používat a podporovat. Pokud nástroj neexistuje, vytvoříme jej a použijeme open source sami. Supabase není mapováním Firebase v poměru 1:1. Naším cílem je poskytnout vývojářům vývojářské prostředí podobné Firebase s využitím nástrojů s otevřeným zdrojovým kódem.

**Architektura**

Supabase je [hostovaná platforma](https://supabase.com/dashboard). Můžete se zaregistrovat a začít používat Supabase, aniž byste museli cokoli instalovat.
Můžete také [hostovat sami](https://supabase.com/docs/guides/hosting/overview) a [vyvíjet lokálně](https://supabase.com/docs/guides/local-development).

![Architektura](https://github.com/supabase/supabase/blob/master/apps/docs/public/img/supabase-architecture.svg)

- [PostgreSQL](https://www.postgresql.org/) je objektově-relační databázový systém s více než 30 lety aktivního vývoje, který si získal dobrou pověst díky spolehlivosti, robustnosti funkcí a výkonu.
- [Realtime](https://github.com/supabase/realtime) je server v jazyce Elixir, který umožňuje naslouchat vkládání, aktualizacím a mazání dat do PostgreSQL pomocí webových soketů. Realtime zjišťuje změny v databázi pomocí replikačních funkcí Postgresu, převádí změny do JSON a poté vysílá JSON přes webové sockety autorizovaným klientům.
- [PostgREST](http://postgrest.org/) je webový server, který mění databázi PostgreSQL přímo na rozhraní RESTful API
- [pg_graphql](http://github.com/supabase/pg_graphql/) je rozšíření PostgreSQL, které vystavuje rozhraní GraphQL API
- [Storage](https://github.com/supabase/storage-api) poskytuje rozhraní RESTful pro správu souborů uložených v S3, přičemž ke správě oprávnění využívá Postgres.
- [Postgres-meta](https://github.com/supabase/postgres-meta) je rozhraní RESTful API pro správu Postgresu, které umožňuje načítat tabulky, přidávat role, spouštět dotazy atd.
- [GoTrue](https://github.com/netlify/gotrue) je rozhraní API založené na SWT pro správu uživatelů a vydávání tokenů SWT.
- [Kong](https://github.com/Kong/kong) je cloudová brána API.

klientské knihovny ####

Náš přístup ke klientským knihovnám je modulární. Každá dílčí knihovna je samostatnou implementací pro jeden externí systém. Je to jeden ze způsobů, jakým podporujeme stávající nástroje.

<table style="table-layout:fixed; white-space: nowrap;">
  <tr>
    <th>Jazyk</th>
    <th>Klient</th>
    <th colspan="5">Feature-Clients (v rámci klienta Supabase)</th>
  </tr>
  
  <tr>
    <th></th>
    <th>Supabase</th>
    <th><a href="https://github.com/postgrest/postgrest" target="_blank" rel="noopener noreferrer">PostgREST</a></th>
    <th><a href="https://github.com/supabase/gotrue" target="_blank" rel="noopener noreferrer">GoTrue</a></th>
    <th><a href="https://github.com/supabase/realtime" target="_blank" rel="noopener noreferrer">Realtime</a></th>
    <th><a href="https://github.com/supabase/storage-api" target="_blank" rel="noopener noreferrer">Storage</a></th>
    <th>Functions</th>
  </tr>
  <!-- TEMPLATE FOR NEW ROW -->
  <!-- START ROW
  <tr>
    <td>lang</td>
    <td><a href="https://github.com/supabase-community/supabase-lang" target="_blank" rel="noopener noreferrer">supabase-lang</a></td>
    <td><a href="https://github.com/supabase-community/postgrest-lang" target="_blank" rel="noopener noreferrer">postgrest-lang</a></td>
    <td><a href="https://github.com/supabase-community/gotrue-lang" target="_blank" rel="noopener noreferrer">gotrue-lang</a></td>
    <td><a href="https://github.com/supabase-community/realtime-lang" target="_blank" rel="noopener noreferrer">realtime-lang</a></td>
    <td><a href="https://github.com/supabase-community/storage-lang" target="_blank" rel="noopener noreferrer">storage-lang</a></td>
  </tr>
  END ROW -->
  
  <th colspan="7">⚡️ Oficiální ⚡️</th>
  
  <tr>
    <td>JavaScript (TypeScript)</td>
    <td><a href="https://github.com/supabase/supabase-js" target="_blank" rel="noopener noreferrer">supabase-js</a></td>
    <td><a href="https://github.com/supabase/supabase-js/tree/master/packages/core/postgrest-js" target="_blank" rel="noopener noreferrer">postgrest-js</a></td>
    <td><a href="https://github.com/supabase/supabase-js/tree/master/packages/core/auth-js" target="_blank" rel="noopener noreferrer">auth-js</a></td>
    <td><a href="https://github.com/supabase/supabase-js/tree/master/packages/core/realtime-js" target="_blank" rel="noopener noreferrer">realtime-js</a></td>
    <td><a href="https://github.com/supabase/supabase-js/tree/master/packages/core/storage-js" target="_blank" rel="noopener noreferrer">storage-js</a></td>
    <td><a href="https://github.com/supabase/supabase-js/tree/master/packages/core/functions-js" target="_blank" rel="noopener noreferrer">functions-js</a></td>
  </tr>
    <tr>
    <td>Flutter</td>
    <td><a href="https://github.com/supabase/supabase-flutter" target="_blank" rel="noopener noreferrer">supabase-flutter</a></td>
    <td><a href="https://github.com/supabase/postgrest-dart" target="_blank" rel="noopener noreferrer">postgrest-dart</a></td>
    <td><a href="https://github.com/supabase/gotrue-dart" target="_blank" rel="noopener noreferrer">gotrue-dart</a></td>
    <td><a href="https://github.com/supabase/realtime-dart" target="_blank" rel="noopener noreferrer">realtime-dart</a></td>
    <td><a href="https://github.com/supabase/storage-dart" target="_blank" rel="noopener noreferrer">storage-dart</a></td>
    <td><a href="https://github.com/supabase/functions-dart" target="_blank" rel="noopener noreferrer">functions-dart</a></td>
  </tr>
  
  <th colspan="7">💚 Komunita 💚</th>
  
  <tr>
    <td>C#</td>
    <td><a href="https://github.com/supabase-community/supabase-csharp" target="_blank" rel="noopener noreferrer">supabase-csharp</a></td>
    <td><a href="https://github.com/supabase-community/postgrest-csharp" target="_blank" rel="noopener noreferrer">postgrest-csharp</a></td>
    <td><a href="https://github.com/supabase-community/gotrue-csharp" target="_blank" rel="noopener noreferrer">gotrue-csharp</a></td>
    <td><a href="https://github.com/supabase-community/realtime-csharp" target="_blank" rel="noopener noreferrer">realtime-csharp</a></td>
    <td><a href="https://github.com/supabase-community/storage-csharp" target="_blank" rel="noopener noreferrer">storage-csharp</a></td>
    <td><a href="https://github.com/supabase-community/functions-csharp" target="_blank" rel="noopener noreferrer">functions-csharp</a></td>
  </tr>
  <tr>
    <td>Go</td>
    <td>-</td>
    <td><a href="https://github.com/supabase-community/postgrest-go" target="_blank" rel="noopener noreferrer">postgrest-go</a></td>
    <td><a href="https://github.com/supabase-community/gotrue-go" target="_blank" rel="noopener noreferrer">gotrue-go</a></td>
    <td>-</td>
    <td><a href="https://github.com/supabase-community/storage-go" target="_blank" rel="noopener noreferrer">storage-go</a></td>
    <td><a href="https://github.com/supabase-community/functions-go" target="_blank" rel="noopener noreferrer">functions-go</a></td>
  </tr>
  <tr>
    <td>Java</td>
    <td>-</td>
    <td>-</td>
    <td><a href="https://github.com/supabase-community/gotrue-java" target="_blank" rel="noopener noreferrer">gotrue-java</a></td>
    <td>-</td>
    <td><a href="https://github.com/supabase-community/storage-java" target="_blank" rel="noopener noreferrer">storage-java</a></td>
    <td>-</td>
  </tr>
  <tr>
    <td>Kotlin</td>
    <td><a href="https://github.com/supabase-community/supabase-kt" target="_blank" rel="noopener noreferrer">supabase-kt</a></td>
    <td><a href="https://github.com/supabase-community/supabase-kt/tree/master/Postgrest" target="_blank" rel="noopener noreferrer">postgrest-kt</a></td>
    <td><a href="https://github.com/supabase-community/supabase-kt/tree/master/GoTrue" target="_blank" rel="noopener noreferrer">gotrue-kt</a></td>
    <td><a href="https://github.com/supabase-community/supabase-kt/tree/master/Realtime" target="_blank" rel="noopener noreferrer">realtime-kt</a></td>
    <td><a href="https://github.com/supabase-community/supabase-kt/tree/master/Storage" target="_blank" rel="noopener noreferrer">storage-kt</a></td>
    <td><a href="https://github.com/supabase-community/supabase-kt/tree/master/Functions" target="_blank" rel="noopener noreferrer">functions-kt</a></td>
  </tr>
  <tr>
    <td>Python</td>
    <td><a href="https://github.com/supabase-community/supabase-py" target="_blank" rel="noopener noreferrer">supabase-py</a></td>
    <td><a href="https://github.com/supabase-community/postgrest-py" target="_blank" rel="noopener noreferrer">postgrest-py</a></td>
    <td><a href="https://github.com/supabase-community/gotrue-py" target="_blank" rel="noopener noreferrer">gotrue-py</a></td>
    <td><a href="https://github.com/supabase-community/realtime-py" target="_blank" rel="noopener noreferrer">realtime-py</a></td>
    <td><a href="https://github.com/supabase-community/storage-py" target="_blank" rel="noopener noreferrer">storage-py</a></td>
    <td><a href="https://github.com/supabase-community/functions-py" target="_blank" rel="noopener noreferrer">functions-py</a></td>
  </tr>
  <tr>
    <td>Ruby</td>
    <td><a href="https://github.com/supabase-community/supabase-rb" target="_blank" rel="noopener noreferrer">supabase-rb</a></td>
    <td><a href="https://github.com/supabase-community/postgrest-rb" target="_blank" rel="noopener noreferrer">postgrest-rb</a></td>
    <td>-</td>
    <td>-</td>
    <td>-</td>
    <td>-</td>
  </tr>
  <tr>
    <td>Rust</td>
    <td>-</td>
    <td><a href="https://github.com/supabase-community/postgrest-rs" target="_blank" rel="noopener noreferrer">postgrest-rs</a></td>
    <td>-</td>
    <td>-</td>
    <td>-</td>
    <td>-</td>
  </tr>
  <tr>
    <td>Swift</td>
    <td><a href="https://github.com/supabase-community/supabase-swift" target="_blank" rel="noopener noreferrer">supabase-swift</a></td>
    <td><a href="https://github.com/supabase-community/postgrest-swift" target="_blank" rel="noopener noreferrer">postgrest-swift</a></td>
    <td><a href="https://github.com/supabase-community/gotrue-swift" target="_blank" rel="noopener noreferrer">gotrue-swift</a></td>
    <td><a href="https://github.com/supabase-community/realtime-swift" target="_blank" rel="noopener noreferrer">realtime-swift</a></td>
    <td><a href="https://github.com/supabase-community/storage-swift" target="_blank" rel="noopener noreferrer">storage-swift</a></td>
    <td><a href="https://github.com/supabase-community/functions-swift" target="_blank" rel="noopener noreferrer">functions-swift</a></td>
  </tr>
  <tr>
    <td>Godot Engine (GDScript)</td>
    <td><a href="https://github.com/supabase-community/godot-engine.supabase" target="_blank" rel="noopener noreferrer">supabase-gdscript</a></td>
    <td><a href="https://github.com/supabase-community/postgrest-gdscript" target="_blank" rel="noopener noreferrer">postgrest-gdscript</a></td>
    <td><a href="https://github.com/supabase-community/gotrue-gdscript" target="_blank" rel="noopener noreferrer">gotrue-gdscript</a></td>
    <td><a href="https://github.com/supabase-community/realtime-gdscript" target="_blank" rel="noopener noreferrer">realtime-gdscript</a></td>
    <td><a href="https://github.com/supabase-community/storage-gdscript" target="_blank" rel="noopener noreferrer">storage-gdscript</a></td>
    <td><a href="https://github.com/supabase-community/functions-gdscript" target="_blank" rel="noopener noreferrer">functions-gdscript</a></td>
  </tr>
  
</table>

<!--- Remove this list if you're translating to another language, it's hard to keep updated across multiple files-->
<!--- Keep only the link to the list of translation files-->

## Překlady

- [Arabština | العربية](/i18n/README.ar.md)
- [Albánština / Shqip](/i18n/README.sq.md)
- [Bangla / বাংলা](/i18n/README.bn.md)
- [Bulharština / Български](/i18n/README.bg.md)
- [Katalánština / Català](/i18n/README.ca.md)
- [Dánština / Dansk](/i18n/README.da.md)
- [Dutch / Nederlands](/i18n/README.nl.md)
- [Angličtina](https://github.com/supabase/supabase)
- [Finsky / Suomalainen](/i18n/README.fi.md)
- [Francouzština / Français](/i18n/README.fr.md)
- [Němčina / Deutsch](/i18n/README.de.md)
- [Řečtina / Ελληνικά](/i18n/README.gr.md)
- [Hebrejština / עברית](/i18n/README.he.md)
- [Hindština / हिंदी](/i18n/README.hi.md)
- [Maďarština / Magyar](/i18n/README.hu.md)
- [Nepálština / नेपाली](/i18n/README.ne.md)
- [Indonéština / Bahasa Indonesia](/i18n/README.id.md)
- [Italština / Italiano](/i18n/README.it.md)
- [Japonština / 日本語](/i18n/README.jp.md)
- [Korejština / 한국어](/i18n/README.ko.md)
- [Malajština / Bahasa Malaysia](/i18n/README.ms.md)
- [Norština (Bokmål) / Norsk (Bokmål)](/i18n/README.nb-no.md)
- [Perština / فارسی](/i18n/README.fa.md)
- [Polština / Polski](/i18n/README.pl.md)
- [Portuguese / Português](/i18n/README.pt.md)
- [Portugalština (brazilská) / Português Brasileiro](/i18n/README.pt-br.md)
- [Rumunština / Română](/i18n/README.ro.md)
- [Russian / Pусский](/i18n/README.ru.md)
- [srbština / Srpski](/i18n/README.sr.md)
- [Sinhálština / සිංහල](/i18n/README.si.md)
- [Spanish / Español](/i18n/README.es.md)
- [Zjednodušená čínština / 简体中文](/i18n/README.zh-cn.md)
- [Švédština / Svenska](/i18n/README.sv.md)
- [Thai / ไทย](/i18n/README.th.md)
- [Tradiční čínština / 繁體中文](/i18n/README.zh-tw.md)
- [Turečtina / Türkçe](/i18n/README.tr.md)
- [Ukrajinština / Українська](/i18n/README.uk.md)
- [Vietnamština / Tiếng Việt](/i18n/README.vi-vn.md)
- [Seznam překladů](/i18n/languages.md) <!--- Keep only this -->

---

## Sponzoři

[![Nový sponzor](https://user-images.githubusercontent.com/10214025/90518111-e74bbb00-e198-11ea-8f88-c9e3c1aa4b5b.png)](https://github.com/sponsors/supabase)


## Links discovered
- [Supabase](https://supabase.com)
- [Dokumenty](https://supabase.com/docs/guides/database)
- [Dokumenty](https://supabase.com/docs/guides/auth)
- [Dokumenty](https://supabase.com/docs/guides/api#rest-api-overview)
- [Dokumenty](https://supabase.com/docs/guides/api#graphql-api-overview)
- [Dokumenty](https://supabase.com/docs/guides/api#realtime-api-overview)
- [Docs](https://supabase.com/docs/guides/database/functions)
- [Docs](https://supabase.com/docs/guides/functions)
- [Dokumenty](https://supabase.com/docs/guides/storage)
- [Supabase Dashboard](https://raw.githubusercontent.com/supabase/supabase/master/apps/www/public/images/github/supabase-dashboard.png)
- [supabase.com/docs](https://supabase.com/docs)
- [Začínáme](https://github.com/supabase/supabase/blob/master/DEVELOPERS.md)
- [Fórum komunity](https://github.com/supabase/supabase/discussions)
- [GitHub Issues](https://github.com/supabase/supabase/issues)
- [E-mailová podpora](https://supabase.com/docs/support#business-support)
- [Discord](https://discord.supabase.com)
- [supabase.com/dashboard](https://supabase.com/dashboard)
- [[status](https://supabase.com/docs/guides/getting-started/features#feature-status)
- [hostovaná platforma](https://supabase.com/dashboard)
- [hostovat sami](https://supabase.com/docs/guides/hosting/overview)
- [vyvíjet lokálně](https://supabase.com/docs/guides/local-development)
- [Architektura](https://github.com/supabase/supabase/blob/master/apps/docs/public/img/supabase-architecture.svg)
- [PostgreSQL](https://www.postgresql.org/)
- [Realtime](https://github.com/supabase/realtime)
- [PostgREST](http://postgrest.org/)
- [pg_graphql](http://github.com/supabase/pg_graphql/)
- [Storage](https://github.com/supabase/storage-api)
- [Postgres-meta](https://github.com/supabase/postgres-meta)
- [GoTrue](https://github.com/netlify/gotrue)
- [Kong](https://github.com/Kong/kong)
- [Arabština | العربية](https://github.com/supabase/supabase/blob/master/i18n/README.ar.md)
- [Albánština / Shqip](https://github.com/supabase/supabase/blob/master/i18n/README.sq.md)
- [Bangla / বাংলা](https://github.com/supabase/supabase/blob/master/i18n/README.bn.md)
- [Bulharština / Български](https://github.com/supabase/supabase/blob/master/i18n/README.bg.md)
- [Katalánština / Català](https://github.com/supabase/supabase/blob/master/i18n/README.ca.md)
- [Dánština / Dansk](https://github.com/supabase/supabase/blob/master/i18n/README.da.md)
- [Dutch / Nederlands](https://github.com/supabase/supabase/blob/master/i18n/README.nl.md)
- [Angličtina](https://github.com/supabase/supabase)
- [Finsky / Suomalainen](https://github.com/supabase/supabase/blob/master/i18n/README.fi.md)
- [Francouzština / Français](https://github.com/supabase/supabase/blob/master/i18n/README.fr.md)
- [Němčina / Deutsch](https://github.com/supabase/supabase/blob/master/i18n/README.de.md)
- [Řečtina / Ελληνικά](https://github.com/supabase/supabase/blob/master/i18n/README.gr.md)
- [Hebrejština / עברית](https://github.com/supabase/supabase/blob/master/i18n/README.he.md)
- [Hindština / हिंदी](https://github.com/supabase/supabase/blob/master/i18n/README.hi.md)
- [Maďarština / Magyar](https://github.com/supabase/supabase/blob/master/i18n/README.hu.md)
- [Nepálština / नेपाली](https://github.com/supabase/supabase/blob/master/i18n/README.ne.md)
- [Indonéština / Bahasa Indonesia](https://github.com/supabase/supabase/blob/master/i18n/README.id.md)
- [Italština / Italiano](https://github.com/supabase/supabase/blob/master/i18n/README.it.md)
- [Japonština / 日本語](https://github.com/supabase/supabase/blob/master/i18n/README.jp.md)
- [Korejština / 한국어](https://github.com/supabase/supabase/blob/master/i18n/README.ko.md)
- [Malajština / Bahasa Malaysia](https://github.com/supabase/supabase/blob/master/i18n/README.ms.md)
- [Norština (Bokmål) / Norsk (Bokmål)](https://github.com/supabase/supabase/blob/master/i18n/README.nb-no.md)
- [Perština / فارسی](https://github.com/supabase/supabase/blob/master/i18n/README.fa.md)
- [Polština / Polski](https://github.com/supabase/supabase/blob/master/i18n/README.pl.md)
- [Portuguese / Português](https://github.com/supabase/supabase/blob/master/i18n/README.pt.md)
- [Portugalština (brazilská) / Português Brasileiro](https://github.com/supabase/supabase/blob/master/i18n/README.pt-br.md)
- [Rumunština / Română](https://github.com/supabase/supabase/blob/master/i18n/README.ro.md)
- [Russian / Pусский](https://github.com/supabase/supabase/blob/master/i18n/README.ru.md)
- [srbština / Srpski](https://github.com/supabase/supabase/blob/master/i18n/README.sr.md)
- [Sinhálština / සිංහල](https://github.com/supabase/supabase/blob/master/i18n/README.si.md)
- [Spanish / Español](https://github.com/supabase/supabase/blob/master/i18n/README.es.md)
- [Zjednodušená čínština / 简体中文](https://github.com/supabase/supabase/blob/master/i18n/README.zh-cn.md)
- [Švédština / Svenska](https://github.com/supabase/supabase/blob/master/i18n/README.sv.md)
- [Thai / ไทย](https://github.com/supabase/supabase/blob/master/i18n/README.th.md)
- [Tradiční čínština / 繁體中文](https://github.com/supabase/supabase/blob/master/i18n/README.zh-tw.md)
- [Turečtina / Türkçe](https://github.com/supabase/supabase/blob/master/i18n/README.tr.md)
- [Ukrajinština / Українська](https://github.com/supabase/supabase/blob/master/i18n/README.uk.md)
- [Vietnamština / Tiếng Việt](https://github.com/supabase/supabase/blob/master/i18n/README.vi-vn.md)
- [Seznam překladů](https://github.com/supabase/supabase/blob/master/i18n/languages.md)
- [![Nový sponzor](https://user-images.githubusercontent.com/10214025/90518111-e74bbb00-e198-11ea-8f88-c9e3c1aa4b5b.png)
- [PostgREST](https://github.com/postgrest/postgrest)
- [GoTrue](https://github.com/supabase/gotrue)
- [supabase-lang](https://github.com/supabase-community/supabase-lang)
- [postgrest-lang](https://github.com/supabase-community/postgrest-lang)
- [gotrue-lang](https://github.com/supabase-community/gotrue-lang)
- [realtime-lang](https://github.com/supabase-community/realtime-lang)
- [storage-lang](https://github.com/supabase-community/storage-lang)
- [supabase-js](https://github.com/supabase/supabase-js)
- [postgrest-js](https://github.com/supabase/supabase-js/tree/master/packages/core/postgrest-js)
- [auth-js](https://github.com/supabase/supabase-js/tree/master/packages/core/auth-js)
- [realtime-js](https://github.com/supabase/supabase-js/tree/master/packages/core/realtime-js)
- [storage-js](https://github.com/supabase/supabase-js/tree/master/packages/core/storage-js)
- [functions-js](https://github.com/supabase/supabase-js/tree/master/packages/core/functions-js)
- [supabase-flutter](https://github.com/supabase/supabase-flutter)
- [postgrest-dart](https://github.com/supabase/postgrest-dart)
- [gotrue-dart](https://github.com/supabase/gotrue-dart)
- [realtime-dart](https://github.com/supabase/realtime-dart)
- [storage-dart](https://github.com/supabase/storage-dart)
- [functions-dart](https://github.com/supabase/functions-dart)
- [supabase-csharp](https://github.com/supabase-community/supabase-csharp)
- [postgrest-csharp](https://github.com/supabase-community/postgrest-csharp)
- [gotrue-csharp](https://github.com/supabase-community/gotrue-csharp)
- [realtime-csharp](https://github.com/supabase-community/realtime-csharp)
- [storage-csharp](https://github.com/supabase-community/storage-csharp)
- [functions-csharp](https://github.com/supabase-community/functions-csharp)
- [postgrest-go](https://github.com/supabase-community/postgrest-go)
- [gotrue-go](https://github.com/supabase-community/gotrue-go)
- [storage-go](https://github.com/supabase-community/storage-go)
- [functions-go](https://github.com/supabase-community/functions-go)
- [gotrue-java](https://github.com/supabase-community/gotrue-java)

--- i18n/README.da.md ---
<p align="center">
<img src="https://user-images.githubusercontent.com/8291514/213727234-cda046d6-28c6-491a-b284-b86c5cede25d.png#gh-light-mode-only">
<img src="https://user-images.githubusercontent.com/8291514/213727225-56186826-bee8-43b5-9b15-86e839d89393.png#gh-dark-mode-only">
</p>

---

# Supabase

[Supabase](https://supabase.com) er et Open Source Firebase-alternativ. Vi opbygger Firebase-funktionerne ved hjælp af open source-værktøjer i virksomhedskvalitet.

- [x] Hosted Postgres Database. [Docs](https://supabase.com/docs/guides/database)
- [x] Autentifikation og autorisering. [Docs](https://supabase.com/docs/guides/auth)
- [x] Automatisk genererede API'er.
  - [x] REST. [Docs](https://supabase.com/docs/guides/api#rest-api-overview)
  - [x] GraphQL. [Docs](https://supabase.com/docs/guides/api#graphql-api-overview)
  - [x] Realtidsabonnementer. [Docs](https://supabase.com/docs/guides/api#realtime-api-overview)
- [x] Funktioner.
  - [x] Databasefunktioner. [Docs](https://supabase.com/docs/guides/database/functions)
  - [x] Edge-funktioner [Docs](https://supabase.com/docs/guides/functions)
- [x] Filopbevaring. [Docs](https://supabase.com/docs/guides/storage)
- [x] Dashboard

![Supabase Dashboard](https://raw.githubusercontent.com/supabase/supabase/master/apps/www/public/images/github/supabase-dashboard.png)

## Dokumentation

Du kan finde den fulde dokumentation på [supabase.com/docs](https://supabase.com/docs)

For at se, hvordan man bidrager, besøg [Getting Started](../DEVELOPERS.md)

## Fællesskab og support

- [Community Forum](https://github.com/supabase/supabase/discussions). Bedst til: hjælp med at bygge, diskussion om bedste praksis for databaser.
- [GitHub Issues](https://github.com/supabase/supabase/issues). Bedst til: fejl og fejl, du støder på ved brug af Supabase.
- [Email Support](https://supabase.com/docs/support#business-support). Bedst til: problemer med din database eller infrastruktur.
- [Discord](https://discord.supabase.com). Bedst til: deling af dine applikationer og hygge med fællesskabet.

## Status

- [x] Alpha: Vi tester Supabase med et lukket sæt af kunder
- [x] Offentlig Alpha: Alle kan tilmelde sig på [supabase.com/dashboard](https://supabase.com/dashboard). Men vær forsigtig med os, der er et par knuder
- [x] Public Beta: Stabil nok til de fleste ikke-virksomhedsrelaterede brugssager
- [ ] Public: Generel tilgængelighed [[status](https://supabase.com/docs/guides/getting-started/features#feature-status)]

Vi er i øjeblikket i Public Beta. Hold øje med "releases" i denne repo for at få besked om større opdateringer.

<kbd><img src="https://raw.githubusercontent.com/supabase/supabase/d5f7f413ab356dc1a92075cb3cee4e40a957d5b1/web/static/watch-repo.gif" alt="Watch this repo"/></kbd>

---

## Sådan fungerer det

Supabase er en kombination af open source-værktøjer. Vi opbygger funktionerne i Firebase ved hjælp af open source-produkter i virksomhedskvalitet. Hvis værktøjerne og fællesskaberne findes med en MIT-, Apache 2- eller tilsvarende åben licens, vil vi bruge og støtte det pågældende værktøj. Hvis værktøjet ikke findes, udvikler og open source-udvikler vi det selv. Supabase er ikke en 1-til-1-mapping af Firebase. Vores mål er at give udviklere en Firebase-lignende udvikleroplevelse ved hjælp af open source-værktøjer.

**Arkitektur**

Supabase er en [hosted platform](https://supabase.com/dashboard). Du kan tilmelde dig og begynde at bruge Supabase uden at installere noget.
Du kan også [selv hoste](https://supabase.com/docs/guides/hosting/overview) og [udvikle lokalt](https://supabase.com/docs/guides/local-development).

![Arkitektur](https://github.com/supabase/supabase/blob/master/apps/docs/public/img/supabase-architecture.svg)

- [PostgreSQL](https://www.postgresql.org/) er et objektrelationelt databasesystem med over 30 års aktiv udvikling, der har givet det et godt ry for pålidelighed, robusthed og ydeevne.
- [Realtime](https://github.com/supabase/realtime) er en Elixir-server, der giver dig mulighed for at lytte til PostgreSQL-indsættelser, -opdateringer og -slettelser ved hjælp af websockets. Realtime spørger Postgres' indbyggede replikationsfunktionalitet efter databaseændringer, konverterer ændringer til JSON og sender derefter JSON'en over websockets til autoriserede klienter.
- [PostgREST](http://postgrest.org/) er en webserver, der forvandler din PostgreSQL-database direkte til et RESTful API
- [pg_graphql](http://github.com/supabase/pg_graphql/) er en PostgreSQL-udvidelse, der eksponerer et GraphQL API
- [Storage](https://github.com/supabase/storage-api) giver en RESTful grænseflade til administration af filer gemt i S3, der bruger Postgres til at administrere tilladelser.
- [postgres-meta](https://github.com/supabase/postgres-meta) er et RESTful API til administration af din Postgres, så du kan hente tabeller, tilføje roller og køre forespørgsler osv.
- [GoTrue](https://github.com/netlify/gotrue) er et SWT-baseret API til administration af brugere og udstedelse af SWT-tokens.
- [Kong](https://github.com/Kong/kong) er en cloud-nativ API-gateway.

#### Klientbiblioteker

Vores tilgang til klientbiblioteker er modulær. Hvert delbibliotek er en selvstændig implementering for et enkelt eksternt system. Dette er en af de måder, hvorpå vi støtter eksisterende værktøjer.

<table style="table-layout:fixed; white-space: nowrap;">
  <tr>
    <th>Sprog</th>
    <th>Klient</th>
    <th colspan="5">Feature-Clients (medtaget i Supabase-klienten)</th>
  </tr>
  
  <tr>
    <th></th>
    <th>Supabase</th>
    <th><a href="https://github.com/postgrest/postgrest" target="_blank" rel="noopener noreferrer">PostgREST</a></th>
    <th><a href="https://github.com/supabase/gotrue" target="_blank" rel="noopener noreferrer">GoTrue</a></th>
    <th><a href="https://github.com/supabase/realtime" target="_blank" rel="noopener noreferrer">Realtime</a></th>
    <th><a href="https://github.com/supabase/storage-api" target="_blank" rel="noopener noreferrer">Storage</a></th>
    <th>Functions</th>
  </tr>
  <!-- TEMPLATE FOR NEW ROW -->
  <!-- START ROW
  <tr>
    <td>lang</td>
    <td><a href="https://github.com/supabase-community/supabase-lang" target="_blank" rel="noopener noreferrer">supabase-lang</a></td>
    <td><a href="https://github.com/supabase-community/postgrest-lang" target="_blank" rel="noopener noreferrer">postgrest-lang</a></td>
    <td><a href="https://github.com/supabase-community/gotrue-lang" target="_blank" rel="noopener noreferrer">gotrue-lang</a></td>
    <td><a href="https://github.com/supabase-community/realtime-lang" target="_blank" rel="noopener noreferrer">realtime-lang</a></td>
    <td><a href="https://github.com/supabase-community/storage-lang" target="_blank" rel="noopener noreferrer">storage-lang</a></td>
  </tr>
  END ROW -->
  
  <th colspan="7">⚡️ Officiel ⚡️</th>
  
  <tr>
    <td>JavaScript (TypeScript)</td>
    <td><a href="https://github.com/supabase/supabase-js" target="_blank" rel="noopener noreferrer">supabase-js</a></td>
    <td><a href="https://github.com/supabase/supabase-js/tree/master/packages/core/postgrest-js" target="_blank" rel="noopener noreferrer">postgrest-js</a></td>
    <td><a href="https://github.com/supabase/supabase-js/tree/master/packages/core/auth-js" target="_blank" rel="noopener noreferrer">auth-js</a></td>
    <td><a href="https://github.com/supabase/supabase-js/tree/master/packages/core/realtime-js" target="_blank" rel="noopener noreferrer">realtime-js</a></td>
    <td><a href="https://github.com/supabase/supabase-js/tree/master/packages/core/storage-js" target="_blank" rel="noopener noreferrer">storage-js</a></td>
    <td><a href="https://github.com/supabase/supabase-js/tree/master/packages/core/functions-js" target="_blank" rel="noopener noreferrer">functions-js</a></td>
  </tr>
    <tr>
    <td>Flutter</td>
    <td><a href="https://github.com/supabase/supabase-flutter" target="_blank" rel="noopener noreferrer">supabase-flutter</a></td>
    <td><a href="https://github.com/supabase/postgrest-dart" target="_blank" rel="noopener noreferrer">postgrest-dart</a></td>
    <td><a href="https://github.com/supabase/gotrue-dart" target="_blank" rel="noopener noreferrer">gotrue-dart</a></td>
    <td><a href="https://github.com/supabase/realtime-dart" target="_blank" rel="noopener noreferrer">realtime-dart</a></td>
    <td><a href="https://github.com/supabase/storage-dart" target="_blank" rel="noopener noreferrer">storage-dart</a></td>
    <td><a href="https://github.com/supabase/functions-dart" target="_blank" rel="noopener noreferrer">functions-dart</a></td>
  </tr>
  
  <th colspan="7">💚 Fællesskab 💚</th>
  
  <tr>
    <td>C#</td>
    <td><a href="https://github.com/supabase-community/supabase-csharp" target="_blank" rel="noopener noreferrer">supabase-csharp</a></td>
    <td><a href="https://github.com/supabase-community/postgrest-csharp" target="_blank" rel="noopener noreferrer">postgrest-csharp</a></td>
    <td><a href="https://github.com/supabase-community/gotrue-csharp" target="_blank" rel="noopener noreferrer">gotrue-csharp</a></td>
    <td><a href="https://github.com/supabase-community/realtime-csharp" target="_blank" rel="noopener noreferrer">realtime-csharp</a></td>
    <td><a href="https://github.com/supabase-community/storage-csharp" target="_blank" rel="noopener noreferrer">storage-csharp</a></td>
    <td><a href="https://github.com/supabase-community/functions-csharp" target="_blank" rel="noopener noreferrer">functions-csharp</a></td>
  </tr>
  <tr>
    <td>Go</td>
    <td>-</td>
    <td><a href="https://github.com/supabase-community/postgrest-go" target="_blank" rel="noopener noreferrer">postgrest-go</a></td>
    <td><a href="https://github.com/supabase-community/gotrue-go" target="_blank" rel="noopener noreferrer">gotrue-go</a></td>
    <td>-</td>
    <td><a href="https://github.com/supabase-community/storage-go" target="_blank" rel="noopener noreferrer">storage-go</a></td>
    <td><a href="https://github.com/supabase-community/functions-go" target="_blank" rel="noopener noreferrer">functions-go</a></td>
  </tr>
  <tr>
    <td>Java</td>
    <td>-</td>
    <td>-</td>
    <td><a href="https://github.com/supabase-community/gotrue-java" target="_blank" rel="noopener noreferrer">gotrue-java</a></td>
    <td>-</td>
    <td><a href="https://github.com/supabase-community/storage-java" target="_blank" rel="noopener noreferrer">storage-java</a></td>
    <td>-</td>
  </tr>
  <tr>
    <td>Kotlin</td>
    <td><a href="https://github.com/supabase-community/supabase-kt" target="_blank" rel="noopener noreferrer">supabase-kt</a></td>
    <td><a href="https://github.com/supabase-community/supabase-kt/tree/master/Postgrest" target="_blank" rel="noopener noreferrer">postgrest-kt</a></td>
    <td><a href="https://github.com/supabase-community/supabase-kt/tree/master/GoTrue" target="_blank" rel="noopener noreferrer">gotrue-kt</a></td>
    <td><a href="https://github.com/supabase-community/supabase-kt/tree/master/Realtime" target="_blank" rel="noopener noreferrer">realtime-kt</a></td>
    <td><a href="https://github.com/supabase-community/supabase-kt/tree/master/Storage" target="_blank" rel="noopener noreferrer">storage-kt</a></td>
    <td><a href="https://github.com/supabase-community/supabase-kt/tree/master/Functions" target="_blank" rel="noopener noreferrer">functions-kt</a></td>
  </tr>
  <tr>
    <td>Python</td>
    <td><a href="https://github.com/supabase-community/supabase-py" target="_blank" rel="noopener noreferrer">supabase-py</a></td>
    <td><a href="https://github.com/supabase-community/postgrest-py" target="_blank" rel="noopener noreferrer">postgrest-py</a></td>
    <td><a href="https://github.com/supabase-community/gotrue-py" target="_blank" rel="noopener noreferrer">gotrue-py</a></td>
    <td><a href="https://github.com/supabase-community/realtime-py" target="_blank" rel="noopener noreferrer">realtime-py</a></td>
    <td><a href="https://github.com/supabase-community/storage-py" target="_blank" rel="noopener noreferrer">storage-py</a></td>
    <td><a href="https://github.com/supabase-community/functions-py" target="_blank" rel="noopener noreferrer">functions-py</a></td>
  </tr>
  <tr>
    <td>Ruby</td>
    <td><a href="https://github.com/supabase-community/supabase-rb" target="_blank" rel="noopener noreferrer">supabase-rb</a></td>
    <td><a href="https://github.com/supabase-community/postgrest-rb" target="_blank" rel="noopener noreferrer">postgrest-rb</a></td>
    <td>-</td>
    <td>-</td>
    <td>-</td>
    <td>-</td>
  </tr>
  <tr>
    <td>Rust</td>
    <td>-</td>
    <td><a href="https://github.com/supabase-community/postgrest-rs" target="_blank" rel="noopener noreferrer">postgrest-rs</a></td>
    <td>-</td>
    <td>-</td>
    <td>-</td>
    <td>-</td>
  </tr>
  <tr>
    <td>Swift</td>
    <td><a href="https://github.com/supabase-community/supabase-swift" target="_blank" rel="noopener noreferrer">supabase-swift</a></td>
    <td><a href="https://github.com/supabase-community/postgrest-swift" target="_blank" rel="noopener noreferrer">postgrest-swift</a></td>
    <td><a href="https://github.com/supabase-community/gotrue-swift" target="_blank" rel="noopener noreferrer">gotrue-swift</a></td>
    <td><a href="https://github.com/supabase-community/realtime-swift" target="_blank" rel="noopener noreferrer">realtime-swift</a></td>
    <td><a href="https://github.com/supabase-community/storage-swift" target="_blank" rel="noopener noreferrer">storage-swift</a></td>
    <td><a href="https://github.com/supabase-community/functions-swift" target="_blank" rel="noopener noreferrer">functions-swift</a></td>
  </tr>
  <tr>
    <td>Godot Engine (GDScript)</td>
    <td><a href="https://github.com/supabase-community/godot-engine.supabase" target="_blank" rel="noopener noreferrer">supabase-gdscript</a></td>
    <td><a href="https://github.com/supabase-community/postgrest-gdscript" target="_blank" rel="noopener noreferrer">postgrest-gdscript</a></td>
    <td><a href="https://github.com/supabase-community/gotrue-gdscript" target="_blank" rel="noopener noreferrer">gotrue-gdscript</a></td>
    <td><a href="https://github.com/supabase-community/realtime-gdscript" target="_blank" rel="noopener noreferrer">realtime-gdscript</a></td>
    <td><a href="https://github.com/supabase-community/storage-gdscript" target="_blank" rel="noopener noreferrer">storage-gdscript</a></td>
    <td><a href="https://github.com/supabase-community/functions-gdscript" target="_blank" rel="noopener noreferrer">functions-gdscript</a></td>
  </tr>
  
</table>

<!--- Remove this list if you're translating to another language, it's hard to keep updated across multiple files-->
<!--- Keep only the link to the list of translation files-->

## Oversættelser

- [Arabisk | العربية](/i18n/README.ar.md)
- [Albansk / Shqip](/i18n/README.sq.md)
- [Bangla / বাংলা](/i18n/README.bn.md)
- [bulgarsk / Български](/i18n/README.bg.md)
- [Catalansk / Català](/i18n/README.ca.md)
- [Danish / Dansk](/i18n/README.da.md)
- [hollandsk / Nederlands](/i18n/README.nl.md)
- [engelsk](https://github.com/supabase/supabase)
- [Finsk / Suomalainen](/i18n/README.fi.md)
- [French / Français](/i18n/README.fr.md)
- [Tysk / Deutsch](/i18n/README.de.md)
- [Græsk / Ελληνικά](/i18n/README.gr.md)
- [Hebraisk / עברית](/i18n/README.he.md)
- [Hindi / हिंदी](/i18n/README.hi.md)
- [Ungarsk / Magyar](/i18n/README.hu.md)
- [Nepali / नेपाली](/i18n/README.ne.md)
- [Indonesisk / Bahasa Indonesia](/i18n/README.id.md)
- [Italiensk / Italiano](/i18n/README.it.md)
- [Japansk / 日本語](/i18n/README.jp.md)
- [koreansk / 한국어](/i18n/README.ko.md)
- [Malay / Bahasa Malaysia](/i18n/README.ms.md)
- [Norsk (Bokmål) / Norsk (Bokmål)](/i18n/README.nb-no.md)
- [Persisk / فارسی](/i18n/README.fa.md)
- [Polsk / Polski](/i18n/README.pl.md)
- [Portugisisk / Português](/i18n/README.pt.md)
- [Portugisisk (brasiliansk) / Português Brasileiro](/i18n/README.pt-br.md)
- [Rumænsk / Română](/i18n/README.ro.md)
- [Russisk / Pусский](/i18n/README.ru.md)
- [Serbisk / Srpski](/i18n/README.sr.md)
- [Sinhala / සිංහල](/i18n/README.si.md)
- [Spanish / Español](/i18n/README.es.md)
- [Forenklet kinesisk / 简体中文](/i18n/README.zh-cn.md)
- [Svensk / Svenska](/i18n/README.sv.md)
- [Thai / ไทย](/i18n/README.th.md)
- [Traditionelt kinesisk / 繁體中文](/i18n/README.zh-tw.md)
- [tyrkisk / Türkçe](/i18n/README.tr.md)
- [Ukrainsk / Українська](/i18n/README.uk.md)
- [Vietnamesisk / Tiếng Việt](/i18n/README.vi-vn.md)
- [Liste over oversættelser](/i18n/languages.md) <!--- Keep only this -->

---

## Sponsorer

[![Ny sponsor](https://user-images.githubusercontent.com/10214025/90518111-e74bbb00-e198-11ea-8f88-c9e3c1aa4b5b.png)](https://github.com/sponsors/supabase)


## Links discovered
- [Supabase](https://supabase.com)
- [Docs](https://supabase.com/docs/guides/database)
- [Docs](https://supabase.com/docs/guides/auth)
- [Docs](https://supabase.com/docs/guides/api#rest-api-overview)
- [Docs](https://supabase.com/docs/guides/api#graphql-api-overview)
- [Docs](https://supabase.com/docs/guides/api#realtime-api-overview)
- [Docs](https://supabase.com/docs/guides/database/functions)
- [Docs](https://supabase.com/docs/guides/functions)
- [Docs](https://supabase.com/docs/guides/storage)
- [Supabase Dashboard](https://raw.githubusercontent.com/supabase/supabase/master/apps/www/public/images/github/supabase-dashboard.png)
- [supabase.com/docs](https://supabase.com/docs)
- [Getting Started](https://github.com/supabase/supabase/blob/master/DEVELOPERS.md)
- [Community Forum](https://github.com/supabase/supabase/discussions)
- [GitHub Issues](https://github.com/supabase/supabase/issues)
- [Email Support](https://supabase.com/docs/support#business-support)
- [Discord](https://discord.supabase.com)
- [supabase.com/dashboard](https://supabase.com/dashboard)
- [[status](https://supabase.com/docs/guides/getting-started/features#feature-status)
- [hosted platform](https://supabase.com/dashboard)
- [selv hoste](https://supabase.com/docs/guides/hosting/overview)
- [udvikle lokalt](https://supabase.com/docs/guides/local-development)
- [Arkitektur](https://github.com/supabase/supabase/blob/master/apps/docs/public/img/supabase-architecture.svg)
- [PostgreSQL](https://www.postgresql.org/)
- [Realtime](https://github.com/supabase/realtime)
- [PostgREST](http://postgrest.org/)
- [pg_graphql](http://github.com/supabase/pg_graphql/)
- [Storage](https://github.com/supabase/storage-api)
- [postgres-meta](https://github.com/supabase/postgres-meta)
- [GoTrue](https://github.com/netlify/gotrue)
- [Kong](https://github.com/Kong/kong)
- [Arabisk | العربية](https://github.com/supabase/supabase/blob/master/i18n/README.ar.md)
- [Albansk / Shqip](https://github.com/supabase/supabase/blob/master/i18n/README.sq.md)
- [Bangla / বাংলা](https://github.com/supabase/supabase/blob/master/i18n/README.bn.md)
- [bulgarsk / Български](https://github.com/supabase/supabase/blob/master/i18n/README.bg.md)
- [Catalansk / Català](https://github.com/supabase/supabase/blob/master/i18n/README.ca.md)
- [Danish / Dansk](https://github.com/supabase/supabase/blob/master/i18n/README.da.md)
- [hollandsk / Nederlands](https://github.com/supabase/supabase/blob/master/i18n/README.nl.md)
- [engelsk](https://github.com/supabase/supabase)
- [Finsk / Suomalainen](https://github.com/supabase/supabase/blob/master/i18n/README.fi.md)
- [French / Français](https://github.com/supabase/supabase/blob/master/i18n/README.fr.md)
- [Tysk / Deutsch](https://github.com/supabase/supabase/blob/master/i18n/README.de.md)
- [Græsk / Ελληνικά](https://github.com/supabase/supabase/blob/master/i18n/README.gr.md)
- [Hebraisk / עברית](https://github.com/supabase/supabase/blob/master/i18n/README.he.md)
- [Hindi / हिंदी](https://github.com/supabase/supabase/blob/master/i18n/README.hi.md)
- [Ungarsk / Magyar](https://github.com/supabase/supabase/blob/master/i18n/README.hu.md)
- [Nepali / नेपाली](https://github.com/supabase/supabase/blob/master/i18n/README.ne.md)
- [Indonesisk / Bahasa Indonesia](https://github.com/supabase/supabase/blob/master/i18n/README.id.md)
- [Italiensk / Italiano](https://github.com/supabase/supabase/blob/master/i18n/README.it.md)
- [Japansk / 日本語](https://github.com/supabase/supabase/blob/master/i18n/README.jp.md)
- [koreansk / 한국어](https://github.com/supabase/supabase/blob/master/i18n/README.ko.md)
- [Malay / Bahasa Malaysia](https://github.com/supabase/supabase/blob/master/i18n/README.ms.md)
- [Norsk (Bokmål) / Norsk (Bokmål)](https://github.com/supabase/supabase/blob/master/i18n/README.nb-no.md)
- [Persisk / فارسی](https://github.com/supabase/supabase/blob/master/i18n/README.fa.md)
- [Polsk / Polski](https://github.com/supabase/supabase/blob/master/i18n/README.pl.md)
- [Portugisisk / Português](https://github.com/supabase/supabase/blob/master/i18n/README.pt.md)
- [Portugisisk (brasiliansk) / Português Brasileiro](https://github.com/supabase/supabase/blob/master/i18n/README.pt-br.md)
- [Rumænsk / Română](https://github.com/supabase/supabase/blob/master/i18n/README.ro.md)
- [Russisk / Pусский](https://github.com/supabase/supabase/blob/master/i18n/README.ru.md)
- [Serbisk / Srpski](https://github.com/supabase/supabase/blob/master/i18n/README.sr.md)
- [Sinhala / සිංහල](https://github.com/supabase/supabase/blob/master/i18n/README.si.md)
- [Spanish / Español](https://github.com/supabase/supabase/blob/master/i18n/README.es.md)
- [Forenklet kinesisk / 简体中文](https://github.com/supabase/supabase/blob/master/i18n/README.zh-cn.md)
- [Svensk / Svenska](https://github.com/supabase/supabase/blob/master/i18n/README.sv.md)
- [Thai / ไทย](https://github.com/supabase/supabase/blob/master/i18n/README.th.md)
- [Traditionelt kinesisk / 繁體中文](https://github.com/supabase/supabase/blob/master/i18n/README.zh-tw.md)
- [tyrkisk / Türkçe](https://github.com/supabase/supabase/blob/master/i18n/README.tr.md)
- [Ukrainsk / Українська](https://github.com/supabase/supabase/blob/master/i18n/README.uk.md)
- [Vietnamesisk / Tiếng Việt](https://github.com/supabase/supabase/blob/master/i18n/README.vi-vn.md)
- [Liste over oversættelser](https://github.com/supabase/supabase/blob/master/i18n/languages.md)
- [![Ny sponsor](https://user-images.githubusercontent.com/10214025/90518111-e74bbb00-e198-11ea-8f88-c9e3c1aa4b5b.png)
- [PostgREST](https://github.com/postgrest/postgrest)
- [GoTrue](https://github.com/supabase/gotrue)
- [supabase-lang](https://github.com/supabase-community/supabase-lang)
- [postgrest-lang](https://github.com/supabase-community/postgrest-lang)
- [gotrue-lang](https://github.com/supabase-community/gotrue-lang)
- [realtime-lang](https://github.com/supabase-community/realtime-lang)
- [storage-lang](https://github.com/supabase-community/storage-lang)
- [supabase-js](https://github.com/supabase/supabase-js)
- [postgrest-js](https://github.com/supabase/supabase-js/tree/master/packages/core/postgrest-js)
- [auth-js](https://github.com/supabase/supabase-js/tree/master/packages/core/auth-js)
- [realtime-js](https://github.com/supabase/supabase-js/tree/master/packages/core/realtime-js)
- [storage-js](https://github.com/supabase/supabase-js/tree/master/packages/core/storage-js)
- [functions-js](https://github.com/supabase/supabase-js/tree/master/packages/core/functions-js)
- [supabase-flutter](https://github.com/supabase/supabase-flutter)
- [postgrest-dart](https://github.com/supabase/postgrest-dart)
- [gotrue-dart](https://github.com/supabase/gotrue-dart)
- [realtime-dart](https://github.com/supabase/realtime-dart)
- [storage-dart](https://github.com/supabase/storage-dart)
- [functions-dart](https://github.com/supabase/functions-dart)
- [supabase-csharp](https://github.com/supabase-community/supabase-csharp)
- [postgrest-csharp](https://github.com/supabase-community/postgrest-csharp)
- [gotrue-csharp](https://github.com/supabase-community/gotrue-csharp)
- [realtime-csharp](https://github.com/supabase-community/realtime-csharp)
- [storage-csharp](https://github.com/supabase-community/storage-csharp)
- [functions-csharp](https://github.com/supabase-community/functions-csharp)
- [postgrest-go](https://github.com/supabase-community/postgrest-go)
- [gotrue-go](https://github.com/supabase-community/gotrue-go)
- [storage-go](https://github.com/supabase-community/storage-go)
- [functions-go](https://github.com/supabase-community/functions-go)
- [gotrue-java](https://github.com/supabase-community/gotrue-java)

--- i18n/README.de.md ---
<p align="center">
<img src="https://user-images.githubusercontent.com/8291514/213727234-cda046d6-28c6-491a-b284-b86c5cede25d.png#gh-light-mode-only">
<img src="https://user-images.githubusercontent.com/8291514/213727225-56186826-bee8-43b5-9b15-86e839d89393.png#gh-dark-mode-only">
</p>

---

# Supabase

[Supabase](https://supabase.com) ist eine Open-Source-Alternative zu Firebase. Wir bauen die Funktionen von Firebase mit Open-Source-Tools für Unternehmen auf.

- [x] Gehostete Postgres-Datenbank. [Docs](https://supabase.com/docs/guides/database)
- [x] Authentifizierung und Autorisierung. [Docs](https://supabase.com/docs/guides/auth)
- [x] Auto-generierte APIs.
  - [x] REST. [Docs](https://supabase.com/docs/guides/api#rest-api-overview)
  - [x] GraphQL. [Docs](https://supabase.com/docs/guides/api#graphql-api-overview)
  - [x] Echtzeit-Abonnements. [Docs](https://supabase.com/docs/guides/api#realtime-api-overview)
- [x] Funktionen.
  - [x] Datenbank-Funktionen. [Docs](https://supabase.com/docs/guides/database/functions)
  - [x] Edge-Funktionen [Docs](https://supabase.com/docs/guides/functions)
- [x] Dateispeicher. [Docs](https://supabase.com/docs/guides/storage)
- [x] Dashboard

![Supabase Dashboard](https://raw.githubusercontent.com/supabase/supabase/master/apps/www/public/images/github/supabase-dashboard.png)

## Dokumentation

Die vollständige Dokumentation finden Sie unter [supabase.com/docs](https://supabase.com/docs)

Wie Sie einen Beitrag leisten können, erfahren Sie unter [Erste Schritte](../DEVELOPERS.md)

## Gemeinschaft &amp; Unterstützung

- [Gemeinschaftsforum](https://github.com/supabase/supabase/discussions). Am besten geeignet für: Hilfe bei der Erstellung, Diskussion über bewährte Datenbankverfahren.
- [GitHub Issues](https://github.com/supabase/supabase/issues). Am besten geeignet für: Bugs und Fehler, auf die Sie bei der Verwendung von Supabase stoßen.
- [E-Mail-Support](https://supabase.com/docs/support#business-support). Am besten für: Probleme mit Ihrer Datenbank oder Infrastruktur.
- [Discord](https://discord.supabase.com). Am besten geeignet für: Austausch von Anwendungen und Austausch mit der Community.

## Status

- [x] Alpha: Wir testen Supabase mit einer geschlossenen Gruppe von Kunden
- [x] Öffentliche Alpha: Jeder kann sich unter [supabase.com/dashboard](https://supabase.com/dashboard) anmelden. Aber seien Sie nachsichtig mit uns, es gibt noch ein paar Macken
- [x] Öffentliche Beta: Stabil genug für die meisten nicht-unternehmerischen Anwendungsfälle
- [Öffentlich: Allgemeine Verfügbarkeit [[status](https://supabase.com/docs/guides/getting-started/features#feature-status)]

Wir befinden uns derzeit in der Public Beta. Beobachten Sie "releases" dieses Repos, um über größere Updates informiert zu werden.

<kbd><img src="https://raw.githubusercontent.com/supabase/supabase/d5f7f413ab356dc1a92075cb3cee4e40a957d5b1/web/static/watch-repo.gif" alt="Watch this repo"/></kbd>

---

## Wie es funktioniert

Supabase ist eine Kombination aus Open-Source-Tools. Wir entwickeln die Funktionen von Firebase unter Verwendung von Open-Source-Produkten in Unternehmensqualität. Wenn die Tools und Communities mit einer MIT-, Apache-2- oder einer vergleichbaren offenen Lizenz existieren, verwenden und unterstützen wir dieses Tool. Wenn es das Tool nicht gibt, entwickeln wir es selbst und stellen es als Open Source zur Verfügung. Supabase ist keine 1:1-Abbildung von Firebase. Unser Ziel ist es, Entwicklern eine Firebase-ähnliche Entwicklungserfahrung mit Open-Source-Tools zu bieten.

**Architektur**

Supabase ist eine [gehostete Plattform](https://supabase.com/dashboard). Sie können sich anmelden und Supabase verwenden, ohne etwas zu installieren.
Sie können auch [selbst hosten](https://supabase.com/docs/guides/hosting/overview) und [lokal entwickeln](https://supabase.com/docs/guides/local-development).

![Architektur](https://github.com/supabase/supabase/blob/master/apps/docs/public/img/supabase-architecture.svg)

- [PostgreSQL](https://www.postgresql.org/) ist ein objektrelationales Datenbanksystem, das seit über 30 Jahren aktiv entwickelt wird und sich einen guten Ruf in Bezug auf Zuverlässigkeit, Robustheit der Funktionen und Leistung erworben hat.
- [Realtime](https://github.com/supabase/realtime) ist ein Elixir-Server, mit dem Sie PostgreSQL-Einsätze, -Updates und -Löschvorgänge über Websockets abhören können. Realtime fragt die in Postgres eingebaute Replikationsfunktionalität nach Datenbankänderungen ab, konvertiert die Änderungen in JSON und sendet dann das JSON über Websockets an autorisierte Clients.
- [PostgREST](http://postgrest.org/) ist ein Webserver, der Ihre PostgreSQL-Datenbank direkt in eine RESTful API verwandelt
- [pg_graphql](http://github.com/supabase/pg_graphql/) ist eine PostgreSQL-Erweiterung, die eine GraphQL-API bereitstellt
- [Storage](https://github.com/supabase/storage-api) bietet eine RESTful-Schnittstelle für die Verwaltung von Dateien, die in S3 gespeichert sind, und nutzt Postgres für die Verwaltung von Berechtigungen.
- [postgres-meta](https://github.com/supabase/postgres-meta) ist eine RESTful-API für die Verwaltung von Postgres, mit der Sie Tabellen abrufen, Rollen hinzufügen, Abfragen ausführen können usw.
- [GoTrue](https://github.com/netlify/gotrue) ist eine SWT-basierte API für die Verwaltung von Benutzern und die Ausgabe von SWT-Tokens.
- [Kong](https://github.com/Kong/kong) ist ein Cloud-natives API-Gateway.

#### Client-Bibliotheken

Unser Ansatz für Client-Bibliotheken ist modular. Jede Unterbibliothek ist eine eigenständige Implementierung für ein einzelnes externes System. Dies ist eine der Möglichkeiten, wie wir bestehende Tools unterstützen.

<table style="table-layout:fixed; white-space: nowrap;">
  <tr>
    <th>Sprache</th>
    <th>Client</th>
    <th colspan="5">Feature-Clients (gebündelt im Supabase-Client)</th>
  </tr>
  
  <tr>
    <th></th>
    <th>Supabase</th>
    <th><a href="https://github.com/postgrest/postgrest" target="_blank" rel="noopener noreferrer">PostgREST</a></th>
    <th><a href="https://github.com/supabase/gotrue" target="_blank" rel="noopener noreferrer">GoTrue</a></th>
    <th><a href="https://github.com/supabase/realtime" target="_blank" rel="noopener noreferrer">Realtime</a></th>
    <th><a href="https://github.com/supabase/storage-api" target="_blank" rel="noopener noreferrer">Storage</a></th>
    <th>Functions</th>
  </tr>
  <!-- TEMPLATE FOR NEW ROW -->
  <!-- START ROW
  <tr>
    <td>lang</td>
    <td><a href="https://github.com/supabase-community/supabase-lang" target="_blank" rel="noopener noreferrer">supabase-lang</a></td>
    <td><a href="https://github.com/supabase-community/postgrest-lang" target="_blank" rel="noopener noreferrer">postgrest-lang</a></td>
    <td><a href="https://github.com/supabase-community/gotrue-lang" target="_blank" rel="noopener noreferrer">gotrue-lang</a></td>
    <td><a href="https://github.com/supabase-community/realtime-lang" target="_blank" rel="noopener noreferrer">realtime-lang</a></td>
    <td><a href="https://github.com/supabase-community/storage-lang" target="_blank" rel="noopener noreferrer">storage-lang</a></td>
  </tr>
  END ROW -->
  
  <th colspan="7">⚡️ Offiziell ⚡️</th>
  
  <tr>
    <td>JavaScript (TypeScript)</td>
    <td><a href="https://github.com/supabase/supabase-js" target="_blank" rel="noopener noreferrer">supabase-js</a></td>
    <td><a href="https://github.com/supabase/supabase-js/tree/master/packages/core/postgrest-js" target="_blank" rel="noopener noreferrer">postgrest-js</a></td>
    <td><a href="https://github.com/supabase/supabase-js/tree/master/packages/core/auth-js" target="_blank" rel="noopener noreferrer">auth-js</a></td>
    <td><a href="https://github.com/supabase/supabase-js/tree/master/packages/core/realtime-js" target="_blank" rel="noopener noreferrer">realtime-js</a></td>
    <td><a href="https://github.com/supabase/supabase-js/tree/master/packages/core/storage-js" target="_blank" rel="noopener noreferrer">storage-js</a></td>
    <td><a href="https://github.com/supabase/supabase-js/tree/master/packages/core/functions-js" target="_blank" rel="noopener noreferrer">functions-js</a></td>
  </tr>
    <tr>
    <td>Flutter</td>
    <td><a href="https://github.com/supabase/supabase-flutter" target="_blank" rel="noopener noreferrer">supabase-flutter</a></td>
    <td><a href="https://github.com/supabase/postgrest-dart" target="_blank" rel="noopener noreferrer">postgrest-dart</a></td>
    <td><a href="https://github.com/supabase/gotrue-dart" target="_blank" rel="noopener noreferrer">gotrue-dart</a></td>
    <td><a href="https://github.com/supabase/realtime-dart" target="_blank" rel="noopener noreferrer">realtime-dart</a></td>
    <td><a href="https://github.com/supabase/storage-dart" target="_blank" rel="noopener noreferrer">storage-dart</a></td>
    <td><a href="https://github.com/supabase/functions-dart" target="_blank" rel="noopener noreferrer">functions-dart</a></td>
  </tr>
  
  <th colspan="7">💚 Gemeinschaft 💚</th>
  
  <tr>
    <td>C#</td>
    <td><a href="https://github.com/supabase-community/supabase-csharp" target="_blank" rel="noopener noreferrer">supabase-csharp</a></td>
    <td><a href="https://github.com/supabase-community/postgrest-csharp" target="_blank" rel="noopener noreferrer">postgrest-csharp</a></td>
    <td><a href="https://github.com/supabase-community/gotrue-csharp" target="_blank" rel="noopener noreferrer">gotrue-csharp</a></td>
    <td><a href="https://github.com/supabase-community/realtime-csharp" target="_blank" rel="noopener noreferrer">realtime-csharp</a></td>
    <td><a href="https://github.com/supabase-community/storage-csharp" target="_blank" rel="noopener noreferrer">storage-csharp</a></td>
    <td><a href="https://github.com/supabase-community/functions-csharp" target="_blank" rel="noopener noreferrer">functions-csharp</a></td>
  </tr>
  <tr>
    <td>Go</td>
    <td>-</td>
    <td><a href="https://github.com/supabase-community/postgrest-go" target="_blank" rel="noopener noreferrer">postgrest-go</a></td>
    <td><a href="https://github.com/supabase-community/gotrue-go" target="_blank" rel="noopener noreferrer">gotrue-go</a></td>
    <td>-</td>
    <td><a href="https://github.com/supabase-community/storage-go" target="_blank" rel="noopener noreferrer">storage-go</a></td>
    <td><a href="https://github.com/supabase-community/functions-go" target="_blank" rel="noopener noreferrer">functions-go</a></td>
  </tr>
  <tr>
    <td>Java</td>
    <td>-</td>
    <td>-</td>
    <td><a href="https://github.com/supabase-community/gotrue-java" target="_blank" rel="noopener noreferrer">gotrue-java</a></td>
    <td>-</td>
    <td><a href="https://github.com/supabase-community/storage-java" target="_blank" rel="noopener noreferrer">storage-java</a></td>
    <td>-</td>
  </tr>
  <tr>
    <td>Kotlin</td>
    <td><a href="https://github.com/supabase-community/supabase-kt" target="_blank" rel="noopener noreferrer">supabase-kt</a></td>
    <td><a href="https://github.com/supabase-community/supabase-kt/tree/master/Postgrest" target="_blank" rel="noopener noreferrer">postgrest-kt</a></td>
    <td><a href="https://github.com/supabase-community/supabase-kt/tree/master/GoTrue" target="_blank" rel="noopener noreferrer">gotrue-kt</a></td>
    <td><a href="https://github.com/supabase-community/supabase-kt/tree/master/Realtime" target="_blank" rel="noopener noreferrer">realtime-kt</a></td>
    <td><a href="https://github.com/supabase-community/supabase-kt/tree/master/Storage" target="_blank" rel="noopener noreferrer">storage-kt</a></td>
    <td><a href="https://github.com/supabase-community/supabase-kt/tree/master/Functions" target="_blank" rel="noopener noreferrer">functions-kt</a></td>
  </tr>
  <tr>
    <td>Python</td>
    <td><a href="https://github.com/supabase-community/supabase-py" target="_blank" rel="noopener noreferrer">supabase-py</a></td>
    <td><a href="https://github.com/supabase-community/postgrest-py" target="_blank" rel="noopener noreferrer">postgrest-py</a></td>
    <td><a href="https://github.com/supabase-community/gotrue-py" target="_blank" rel="noopener noreferrer">gotrue-py</a></td>
    <td><a href="https://github.com/supabase-community/realtime-py" target="_blank" rel="noopener noreferrer">realtime-py</a></td>
    <td><a href="https://github.com/supabase-community/storage-py" target="_blank" rel="noopener noreferrer">storage-py</a></td>
    <td><a href="https://github.com/supabase-community/functions-py" target="_blank" rel="noopener noreferrer">functions-py</a></td>
  </tr>
  <tr>
    <td>Ruby</td>
    <td><a href="https://github.com/supabase-community/supabase-rb" target="_blank" rel="noopener noreferrer">supabase-rb</a></td>
    <td><a href="https://github.com/supabase-community/postgrest-rb" target="_blank" rel="noopener noreferrer">postgrest-rb</a></td>
    <td>-</td>
    <td>-</td>
    <td>-</td>
    <td>-</td>
  </tr>
  <tr>
    <td>Rust</td>
    <td>-</td>
    <td><a href="https://github.com/supabase-community/postgrest-rs" target="_blank" rel="noopener noreferrer">postgrest-rs</a></td>
    <td>-</td>
    <td>-</td>
    <td>-</td>
    <td>-</td>
  </tr>
  <tr>
    <td>Swift</td>
    <td><a href="https://github.com/supabase-community/supabase-swift" target="_blank" rel="noopener noreferrer">supabase-swift</a></td>
    <td><a href="https://github.com/supabase-community/postgrest-swift" target="_blank" rel="noopener noreferrer">postgrest-swift</a></td>
    <td><a href="https://github.com/supabase-community/gotrue-swift" target="_blank" rel="noopener noreferrer">gotrue-swift</a></td>
    <td><a href="https://github.com/supabase-community/realtime-swift" target="_blank" rel="noopener noreferrer">realtime-swift</a></td>
    <td><a href="https://github.com/supabase-community/storage-swift" target="_blank" rel="noopener noreferrer">storage-swift</a></td>
    <td><a href="https://github.com/supabase-community/functions-swift" target="_blank" rel="noopener noreferrer">functions-swift</a></td>
  </tr>
  <tr>
    <td>Godot Engine (GDScript)</td>
    <td><a href="https://github.com/supabase-community/godot-engine.supabase" target="_blank" rel="noopener noreferrer">supabase-gdscript</a></td>
    <td><a href="https://github.com/supabase-community/postgrest-gdscript" target="_blank" rel="noopener noreferrer">postgrest-gdscript</a></td>
    <td><a href="https://github.com/supabase-community/gotrue-gdscript" target="_blank" rel="noopener noreferrer">gotrue-gdscript</a></td>
    <td><a href="https://github.com/supabase-community/realtime-gdscript" target="_blank" rel="noopener noreferrer">realtime-gdscript</a></td>
    <td><a href="https://github.com/supabase-community/storage-gdscript" target="_blank" rel="noopener noreferrer">storage-gdscript</a></td>
    <td><a href="https://github.com/supabase-community/functions-gdscript" target="_blank" rel="noopener noreferrer">functions-gdscript</a></td>
  </tr>
  
</table>

<!--- Remove this list if you're translating to another language, it's hard to keep updated across multiple files-->
<!--- Keep only the link to the list of translation files-->

## Übersetzungen

- [Arabisch | العربية](/i18n/README.ar.md)
- [Albanisch / Shqip](/i18n/README.sq.md)
- [Bangla / বাংলা](/i18n/README.bn.md)
- [Bulgarisch / Български](/i18n/README.bg.md)
- [Katalanisch / Català](/i18n/README.ca.md)
- [Dänisch / Dansk](/i18n/README.da.md)
- [Niederländisch / Nederlands](/i18n/README.nl.md)
- [Englisch](https://github.com/supabase/supabase)
- [Finnisch / Suomalainen](/i18n/README.fi.md)
- [Französisch / Français](/i18n/README.fr.md)
- [Deutsch / Deutsch](/i18n/README.de.md)
- [Griechisch / Ελληνικά](/i18n/README.gr.md)
- [Hebräisch / עברית](/i18n/README.he.md)
- [Hindi / हिंदी](/i18n/README.hi.md)
- [Ungarisch / Magyar](/i18n/README.hu.md)
- [Nepali / नेपाली](/i18n/README.ne.md)
- [Indonesisch / Bahasa Indonesia](/i18n/README.id.md)
- [Italienisch / Italiano](/i18n/README.it.md)
- [Japanisch / 日本語](/i18n/README.jp.md)
- [Koreanisch / 한국어](/i18n/README.ko.md)
- [Malaiisch / Bahasa Malaysia](/i18n/README.ms.md)
- [Norwegisch (Bokmål) / Norsk (Bokmål)](/i18n/README.nb-no.md)
- [Persisch / فارسی](/i18n/README.fa.md)
- [Polnisch / Polski](/i18n/README.pl.md)
- [Portugiesisch / Português](/i18n/README.pt.md)
- [Portugiesisch (Brasilianisch) / Português Brasileiro](/i18n/README.pt-br.md)
- [Rumänisch / Română](/i18n/README.ro.md)
- [Russisch / Pусский](/i18n/README.ru.md)
- [Serbisch / Srpski](/i18n/README.sr.md)
- [Singhalesisch / සිංහල](/i18n/README.si.md)
- [Spanisch / Español](/i18n/README.es.md)
- [Vereinfachtes Chinesisch / 简体中文](/i18n/README.zh-cn.md)
- [Schwedisch / Svenska](/i18n/README.sv.md)
- [Thai / ไทย](/i18n/README.th.md)
- [Traditionelles Chinesisch / 繁體中文](/i18n/README.zh-tw.md)
- [Türkisch / Türkçe](/i18n/README.tr.md)
- [Ukrainisch / Українська](/i18n/README.uk.md)
- [Vietnamesisch / Tiếng Việt](/i18n/README.vi-vn.md)
- [Liste der Übersetzungen](/i18n/languages.md) <!--- Keep only this -->

---

## Förderer

[![Neuer Sponsor](https://user-images.githubusercontent.com/10214025/90518111-e74bbb00-e198-11ea-8f88-c9e3c1aa4b5b.png)](https://github.com/sponsors/supabase)


## Links discovered
- [Supabase](https://supabase.com)
- [Docs](https://supabase.com/docs/guides/database)
- [Docs](https://supabase.com/docs/guides/auth)
- [Docs](https://supabase.com/docs/guides/api#rest-api-overview)
- [Docs](https://supabase.com/docs/guides/api#graphql-api-overview)
- [Docs](https://supabase.com/docs/guides/api#realtime-api-overview)
- [Docs](https://supabase.com/docs/guides/database/functions)
- [Docs](https://supabase.com/docs/guides/functions)
- [Docs](https://supabase.com/docs/guides/storage)
- [Supabase Dashboard](https://raw.githubusercontent.com/supabase/supabase/master/apps/www/public/images/github/supabase-dashboard.png)
- [supabase.com/docs](https://supabase.com/docs)
- [Erste Schritte](https://github.com/supabase/supabase/blob/master/DEVELOPERS.md)
- [Gemeinschaftsforum](https://github.com/supabase/supabase/discussions)
- [GitHub Issues](https://github.com/supabase/supabase/issues)
- [E-Mail-Support](https://supabase.com/docs/support#business-support)
- [Discord](https://discord.supabase.com)
- [supabase.com/dashboard](https://supabase.com/dashboard)
- [Öffentlich: Allgemeine Verfügbarkeit [[status](https://supabase.com/docs/guides/getting-started/features#feature-status)
- [gehostete Plattform](https://supabase.com/dashboard)
- [selbst hosten](https://supabase.com/docs/guides/hosting/overview)
- [lokal entwickeln](https://supabase.com/docs/guides/local-development)
- [Architektur](https://github.com/supabase/supabase/blob/master/apps/docs/public/img/supabase-architecture.svg)
- [PostgreSQL](https://www.postgresql.org/)
- [Realtime](https://github.com/supabase/realtime)
- [PostgREST](http://postgrest.org/)
- [pg_graphql](http://github.com/supabase/pg_graphql/)
- [Storage](https://github.com/supabase/storage-api)
- [postgres-meta](https://github.com/supabase/postgres-meta)
- [GoTrue](https://github.com/netlify/gotrue)
- [Kong](https://github.com/Kong/kong)
- [Arabisch | العربية](https://github.com/supabase/supabase/blob/master/i18n/README.ar.md)
- [Albanisch / Shqip](https://github.com/supabase/supabase/blob/master/i18n/README.sq.md)
- [Bangla / বাংলা](https://github.com/supabase/supabase/blob/master/i18n/README.bn.md)
- [Bulgarisch / Български](https://github.com/supabase/supabase/blob/master/i18n/README.bg.md)
- [Katalanisch / Català](https://github.com/supabase/supabase/blob/master/i18n/README.ca.md)
- [Dänisch / Dansk](https://github.com/supabase/supabase/blob/master/i18n/README.da.md)
- [Niederländisch / Nederlands](https://github.com/supabase/supabase/blob/master/i18n/README.nl.md)
- [Englisch](https://github.com/supabase/supabase)
- [Finnisch / Suomalainen](https://github.com/supabase/supabase/blob/master/i18n/README.fi.md)
- [Französisch / Français](https://github.com/supabase/supabase/blob/master/i18n/README.fr.md)
- [Deutsch / Deutsch](https://github.com/supabase/supabase/blob/master/i18n/README.de.md)
- [Griechisch / Ελληνικά](https://github.com/supabase/supabase/blob/master/i18n/README.gr.md)
- [Hebräisch / עברית](https://github.com/supabase/supabase/blob/master/i18n/README.he.md)
- [Hindi / हिंदी](https://github.com/supabase/supabase/blob/master/i18n/README.hi.md)
- [Ungarisch / Magyar](https://github.com/supabase/supabase/blob/master/i18n/README.hu.md)
- [Nepali / नेपाली](https://github.com/supabase/supabase/blob/master/i18n/README.ne.md)
- [Indonesisch / Bahasa Indonesia](https://github.com/supabase/supabase/blob/master/i18n/README.id.md)
- [Italienisch / Italiano](https://github.com/supabase/supabase/blob/master/i18n/README.it.md)
- [Japanisch / 日本語](https://github.com/supabase/supabase/blob/master/i18n/README.jp.md)
- [Koreanisch / 한국어](https://github.com/supabase/supabase/blob/master/i18n/README.ko.md)
- [Malaiisch / Bahasa Malaysia](https://github.com/supabase/supabase/blob/master/i18n/README.ms.md)
- [Norwegisch (Bokmål) / Norsk (Bokmål)](https://github.com/supabase/supabase/blob/master/i18n/README.nb-no.md)
- [Persisch / فارسی](https://github.com/supabase/supabase/blob/master/i18n/README.fa.md)
- [Polnisch / Polski](https://github.com/supabase/supabase/blob/master/i18n/README.pl.md)
- [Portugiesisch / Português](https://github.com/supabase/supabase/blob/master/i18n/README.pt.md)
- [Portugiesisch (Brasilianisch) / Português Brasileiro](https://github.com/supabase/supabase/blob/master/i18n/README.pt-br.md)
- [Rumänisch / Română](https://github.com/supabase/supabase/blob/master/i18n/README.ro.md)
- [Russisch / Pусский](https://github.com/supabase/supabase/blob/master/i18n/README.ru.md)
- [Serbisch / Srpski](https://github.com/supabase/supabase/blob/master/i18n/README.sr.md)
- [Singhalesisch / සිංහල](https://github.com/supabase/supabase/blob/master/i18n/README.si.md)
- [Spanisch / Español](https://github.com/supabase/supabase/blob/master/i18n/README.es.md)
- [Vereinfachtes Chinesisch / 简体中文](https://github.com/supabase/supabase/blob/master/i18n/README.zh-cn.md)
- [Schwedisch / Svenska](https://github.com/supabase/supabase/blob/master/i18n/README.sv.md)
- [Thai / ไทย](https://github.com/supabase/supabase/blob/master/i18n/README.th.md)
- [Traditionelles Chinesisch / 繁體中文](https://github.com/supabase/supabase/blob/master/i18n/README.zh-tw.md)
- [Türkisch / Türkçe](https://github.com/supabase/supabase/blob/master/i18n/README.tr.md)
- [Ukrainisch / Українська](https://github.com/supabase/supabase/blob/master/i18n/README.uk.md)
- [Vietnamesisch / Tiếng Việt](https://github.com/supabase/supabase/blob/master/i18n/README.vi-vn.md)
- [Liste der Übersetzungen](https://github.com/supabase/supabase/blob/master/i18n/languages.md)
- [![Neuer Sponsor](https://user-images.githubusercontent.com/10214025/90518111-e74bbb00-e198-11ea-8f88-c9e3c1aa4b5b.png)
- [PostgREST](https://github.com/postgrest/postgrest)
- [GoTrue](https://github.com/supabase/gotrue)
- [supabase-lang](https://github.com/supabase-community/supabase-lang)
- [postgrest-lang](https://github.com/supabase-community/postgrest-lang)
- [gotrue-lang](https://github.com/supabase-community/gotrue-lang)
- [realtime-lang](https://github.com/supabase-community/realtime-lang)
- [storage-lang](https://github.com/supabase-community/storage-lang)
- [supabase-js](https://github.com/supabase/supabase-js)
- [postgrest-js](https://github.com/supabase/supabase-js/tree/master/packages/core/postgrest-js)
- [auth-js](https://github.com/supabase/supabase-js/tree/master/packages/core/auth-js)
- [realtime-js](https://github.com/supabase/supabase-js/tree/master/packages/core/realtime-js)
- [storage-js](https://github.com/supabase/supabase-js/tree/master/packages/core/storage-js)
- [functions-js](https://github.com/supabase/supabase-js/tree/master/packages/core/functions-js)
- [supabase-flutter](https://github.com/supabase/supabase-flutter)
- [postgrest-dart](https://github.com/supabase/postgrest-dart)
- [gotrue-dart](https://github.com/supabase/gotrue-dart)
- [realtime-dart](https://github.com/supabase/realtime-dart)
- [storage-dart](https://github.com/supabase/storage-dart)
- [functions-dart](https://github.com/supabase/functions-dart)
- [supabase-csharp](https://github.com/supabase-community/supabase-csharp)
- [postgrest-csharp](https://github.com/supabase-community/postgrest-csharp)
- [gotrue-csharp](https://github.com/supabase-community/gotrue-csharp)
- [realtime-csharp](https://github.com/supabase-community/realtime-csharp)
- [storage-csharp](https://github.com/supabase-community/storage-csharp)
- [functions-csharp](https://github.com/supabase-community/functions-csharp)
- [postgrest-go](https://github.com/supabase-community/postgrest-go)
- [gotrue-go](https://github.com/supabase-community/gotrue-go)
- [storage-go](https://github.com/supabase-community/storage-go)
- [functions-go](https://github.com/supabase-community/functions-go)
- [gotrue-java](https://github.com/supabase-community/gotrue-java)

--- i18n/README.el.md ---
<p align="center">
<img src="https://user-images.githubusercontent.com/8291514/213727234-cda046d6-28c6-491a-b284-b86c5cede25d.png#gh-light-mode-only">
<img src="https://user-images.githubusercontent.com/8291514/213727225-56186826-bee8-43b5-9b15-86e839d89393.png#gh-dark-mode-only">
</p>

---

# Supabase

[Supabase](https://supabase.com) είναι μια εναλλακτική λύση της Firebase ανοιχτού κώδικα. Χτίζουμε τα χαρακτηριστικά της Firebase χρησιμοποιώντας εργαλεία ανοιχτού κώδικα επιχειρηματικού επιπέδου.

- [x] Hosted Postgres Database. [Docs](https://supabase.com/docs/guides/database)
- [x] Αυθεντικοποίηση και εξουσιοδότηση. [Έγγραφα](https://supabase.com/docs/guides/auth)
- [x] Αυτόματα παραγόμενα APIs.
  - [x] REST. [Έγγραφα](https://supabase.com/docs/guides/api#rest-api-overview)
  - [x] GraphQL. [Docs](https://supabase.com/docs/guides/api#graphql-api-overview)
  - [x] Συνδρομές σε πραγματικό χρόνο. [Έγγραφα](https://supabase.com/docs/guides/api#realtime-api-overview)
- [x] Συναρτήσεις.
  - [x] Συναρτήσεις βάσης δεδομένων. [Docs](https://supabase.com/docs/guides/database/functions)
  - [x] Edge Functions [Docs](https://supabase.com/docs/guides/functions)
- [x] Αποθήκευση αρχείων. [Docs](https://supabase.com/docs/guides/storage)
- [x] Ταμπλό

![Supabase Dashboard](https://raw.githubusercontent.com/supabase/supabase/master/apps/www/public/images/github/supabase-dashboard.png)

## Τεκμηρίωση

Για πλήρη τεκμηρίωση, επισκεφθείτε τη διεύθυνση [supabase.com/docs](https://supabase.com/docs)

Για να δείτε πώς μπορείτε να συνεισφέρετε, επισκεφθείτε το [Getting Started](../DEVELOPERS.md)

## Κοινότητα &amp; Υποστήριξη

- [Community Forum](https://github.com/supabase/supabase/discussions). Το καλύτερο για: βοήθεια με την κατασκευή, συζήτηση σχετικά με τις βέλτιστες πρακτικές της βάσης δεδομένων.
- [GitHub Issues](https://github.com/supabase/supabase/issues). Το καλύτερο για: σφάλματα και λάθη που αντιμετωπίζετε χρησιμοποιώντας την Supabase.
- [Email Support](https://supabase.com/docs/support#business-support). Το καλύτερο για: προβλήματα με τη βάση δεδομένων ή την υποδομή σας.
- [Discord](https://discord.supabase.com). Το καλύτερο για: να μοιράζεστε τις εφαρμογές σας και να κάνετε παρέα με την κοινότητα.

## Κατάσταση

- [x] Alpha: Δοκιμάζουμε το Supabase με ένα κλειστό σύνολο πελατών
- [x] Δημόσια Alpha: [supabase.com/dashboard](https://supabase.com/dashboard). Αλλά να είστε προσεκτικοί μαζί μας, υπάρχουν μερικές ατέλειες
- [x] Δημόσια Beta: Αρκετά σταθερό για τις περισσότερες περιπτώσεις μη επιχειρηματικής χρήσης
- [ ] Δημόσια: [[status](https://supabase.com/docs/guides/getting-started/features#feature-status)]

Βρισκόμαστε επί του παρόντος σε Public Beta. Παρακολουθήστε τις "κυκλοφορίες" αυτού του repo για να ειδοποιηθείτε για σημαντικές ενημερώσεις.

<kbd><img src="https://raw.githubusercontent.com/supabase/supabase/d5f7f413ab356dc1a92075cb3cee4e40a957d5b1/web/static/watch-repo.gif" alt="Watch this repo"/></kbd>

---

## Πώς λειτουργεί

Η Supabase είναι ένας συνδυασμός εργαλείων ανοικτού κώδικα. Χτίζουμε τα χαρακτηριστικά της Firebase χρησιμοποιώντας προϊόντα ανοικτού κώδικα επιχειρηματικού επιπέδου. Εάν τα εργαλεία και οι κοινότητες υπάρχουν, με άδεια MIT, Apache 2 ή ισοδύναμη ανοιχτή άδεια, θα χρησιμοποιήσουμε και θα υποστηρίξουμε το εργαλείο αυτό. Εάν το εργαλείο δεν υπάρχει, το κατασκευάζουμε και το διαθέτουμε σε ανοιχτό κώδικα μόνοι μας. Η Supabase δεν είναι μια αντιστοίχιση 1 προς 1 της Firebase. Στόχος μας είναι να δώσουμε στους προγραμματιστές μια εμπειρία προγραμματιστή παρόμοια με αυτή της Firebase, χρησιμοποιώντας εργαλεία ανοιχτού κώδικα.

**Αρχιτεκτονική**

Η Supabase είναι μια [φιλοξενούμενη πλατφόρμα](https://supabase.com/dashboard). Μπορείτε να εγγραφείτε και να αρχίσετε να χρησιμοποιείτε το Supabase χωρίς να εγκαταστήσετε τίποτα.
Μπορείτε επίσης να κάνετε [αυτο-ξενάγηση](https://supabase.com/docs/guides/hosting/overview) και [ανάπτυξη τοπικά](https://supabase.com/docs/guides/local-development).

![Αρχιτεκτονική](https://github.com/supabase/supabase/blob/master/apps/docs/public/img/supabase-architecture.svg)

- η [PostgreSQL](https://www.postgresql.org/) είναι ένα αντικειμενο-σχεσιακό σύστημα βάσεων δεδομένων με πάνω από 30 χρόνια ενεργής ανάπτυξης που του έχει αποφέρει μια ισχυρή φήμη για την αξιοπιστία, την ευρωστία των χαρακτηριστικών και την απόδοση.
- [Realtime](https://github.com/supabase/realtime) είναι ένας διακομιστής Elixir που σας επιτρέπει να ακούτε τις εισαγωγές, ενημερώσεις και διαγραφές της PostgreSQL χρησιμοποιώντας websockets. Το Realtime ρωτάει την ενσωματωμένη λειτουργία αντιγραφής της Postgres για αλλαγές στη βάση δεδομένων, μετατρέπει τις αλλαγές σε JSON και στη συνέχεια μεταδίδει το JSON μέσω websockets σε εξουσιοδοτημένους πελάτες.
- [PostgREST](http://postgrest.org/) είναι ένας διακομιστής ιστού που μετατρέπει τη βάση δεδομένων PostgreSQL απευθείας σε ένα RESTful API
- [pg_graphql](http://github.com/supabase/pg_graphql/) μια επέκταση της PostgreSQL που εκθέτει ένα GraphQL API
- [Storage](https://github.com/supabase/storage-api) παρέχει μια RESTful διεπαφή για τη διαχείριση αρχείων που είναι αποθηκευμένα στο S3, χρησιμοποιώντας το Postgres για τη διαχείριση των δικαιωμάτων.
- [postgres-meta](https://github.com/supabase/postgres-meta) είναι ένα RESTful API για τη διαχείριση του Postgres σας, επιτρέποντάς σας να αντλείτε πίνακες, να προσθέτετε ρόλους και να εκτελείτε ερωτήματα κ.λπ.
- το [GoTrue](https://github.com/netlify/gotrue) είναι ένα API βασισμένο στο SWT για τη διαχείριση χρηστών και την έκδοση SWT tokens.
- το [Kong](https://github.com/Kong/kong) είναι μια πύλη API cloud-native.

#### Βιβλιοθήκες πελατών

Η προσέγγισή μας για τις βιβλιοθήκες πελατών είναι αρθρωτή. Κάθε υπο-βιβλιοθήκη είναι μια αυτόνομη υλοποίηση για ένα μόνο εξωτερικό σύστημα. Αυτός είναι ένας από τους τρόπους με τους οποίους υποστηρίζουμε τα υπάρχοντα εργαλεία.

<table style="table-layout:fixed; white-space: nowrap;">
  <tr>
    <th>Γλώσσα</th>
    <th>Πελάτης</th>
    <th colspan="5">Πελάτες-χαρακτηριστικά (που περιλαμβάνονται στον πελάτη Supabase)</th>
  </tr>
  
  <tr>
    <th></th>
    <th>Supabase</th>
    <th><a href="https://github.com/postgrest/postgrest" target="_blank" rel="noopener noreferrer">PostgREST</a></th>
    <th><a href="https://github.com/supabase/gotrue" target="_blank" rel="noopener noreferrer">GoTrue</a></th>
    <th><a href="https://github.com/supabase/realtime" target="_blank" rel="noopener noreferrer">Realtime</a></th>
    <th><a href="https://github.com/supabase/storage-api" target="_blank" rel="noopener noreferrer">Storage</a></th>
    <th>Functions</th>
  </tr>
  <!-- TEMPLATE FOR NEW ROW -->
  <!-- START ROW
  <tr>
    <td>lang</td>
    <td><a href="https://github.com/supabase-community/supabase-lang" target="_blank" rel="noopener noreferrer">supabase-lang</a></td>
    <td><a href="https://github.com/supabase-community/postgrest-lang" target="_blank" rel="noopener noreferrer">postgrest-lang</a></td>
    <td><a href="https://github.com/supabase-community/gotrue-lang" target="_blank" rel="noopener noreferrer">gotrue-lang</a></td>
    <td><a href="https://github.com/supabase-community/realtime-lang" target="_blank" rel="noopener noreferrer">realtime-lang</a></td>
    <td><a href="https://github.com/supabase-community/storage-lang" target="_blank" rel="noopener noreferrer">storage-lang</a></td>
  </tr>
  END ROW -->
  
  <th colspan="7">⚡️ Επίσημο ⚡️</th>
  
  <tr>
    <td>JavaScript (TypeScript)</td>
    <td><a href="https://github.com/supabase/supabase-js" target="_blank" rel="noopener noreferrer">supabase-js</a></td>
    <td><a href="https://github.com/supabase/supabase-js/tree/master/packages/core/postgrest-js" target="_blank" rel="noopener noreferrer">postgrest-js</a></td>
    <td><a href="https://github.com/supabase/supabase-js/tree/master/packages/core/auth-js" target="_blank" rel="noopener noreferrer">auth-js</a></td>
    <td><a href="https://github.com/supabase/supabase-js/tree/master/packages/core/realtime-js" target="_blank" rel="noopener noreferrer">realtime-js</a></td>
    <td><a href="https://github.com/supabase/supabase-js/tree/master/packages/core/storage-js" target="_blank" rel="noopener noreferrer">storage-js</a></td>
    <td><a href="https://github.com/supabase/supabase-js/tree/master/packages/core/functions-js" target="_blank" rel="noopener noreferrer">functions-js</a></td>
  </tr>
    <tr>
    <td>Flutter</td>
    <td><a href="https://github.com/supabase/supabase-flutter" target="_blank" rel="noopener noreferrer">supabase-flutter</a></td>
    <td><a href="https://github.com/supabase/postgrest-dart" target="_blank" rel="noopener noreferrer">postgrest-dart</a></td>
    <td><a href="https://github.com/supabase/gotrue-dart" target="_blank" rel="noopener noreferrer">gotrue-dart</a></td>
    <td><a href="https://github.com/supabase/realtime-dart" target="_blank" rel="noopener noreferrer">realtime-dart</a></td>
    <td><a href="https://github.com/supabase/storage-dart" target="_blank" rel="noopener noreferrer">storage-dart</a></td>
    <td><a href="https://github.com/supabase/functions-dart" target="_blank" rel="noopener noreferrer">functions-dart</a></td>
  </tr>
  
  <th colspan="7">💚 Κοινότητα 💚</th>
  
  <tr>
    <td>C#</td>
    <td><a href="https://github.com/supabase-community/supabase-csharp" target="_blank" rel="noopener noreferrer">supabase-csharp</a></td>
    <td><a href="https://github.com/supabase-community/postgrest-csharp" target="_blank" rel="noopener noreferrer">postgrest-csharp</a></td>
    <td><a href="https://github.com/supabase-community/gotrue-csharp" target="_blank" rel="noopener noreferrer">gotrue-csharp</a></td>
    <td><a href="https://github.com/supabase-community/realtime-csharp" target="_blank" rel="noopener noreferrer">realtime-csharp</a></td>
    <td><a href="https://github.com/supabase-community/storage-csharp" target="_blank" rel="noopener noreferrer">storage-csharp</a></td>
    <td><a href="https://github.com/supabase-community/functions-csharp" target="_blank" rel="noopener noreferrer">functions-csharp</a></td>
  </tr>
  <tr>
    <td>Go</td>
    <td>-</td>
    <td><a href="https://github.com/supabase-community/postgrest-go" target="_blank" rel="noopener noreferrer">postgrest-go</a></td>
    <td><a href="https://github.com/supabase-community/gotrue-go" target="_blank" rel="noopener noreferrer">gotrue-go</a></td>
    <td>-</td>
    <td><a href="https://github.com/supabase-community/storage-go" target="_blank" rel="noopener noreferrer">storage-go</a></td>
    <td><a href="https://github.com/supabase-community/functions-go" target="_blank" rel="noopener noreferrer">functions-go</a></td>
  </tr>
  <tr>
    <td>Java</td>
    <td>-</td>
    <td>-</td>
    <td><a href="https://github.com/supabase-community/gotrue-java" target="_blank" rel="noopener noreferrer">gotrue-java</a></td>
    <td>-</td>
    <td><a href="https://github.com/supabase-community/storage-java" target="_blank" rel="noopener noreferrer">storage-java</a></td>
    <td>-</td>
  </tr>
  <tr>
    <td>Kotlin</td>
    <td><a href="https://github.com/supabase-community/supabase-kt" target="_blank" rel="noopener noreferrer">supabase-kt</a></td>
    <td><a href="https://github.com/supabase-community/supabase-kt/tree/master/Postgrest" target="_blank" rel="noopener noreferrer">postgrest-kt</a></td>
    <td><a href="https://github.com/supabase-community/supabase-kt/tree/master/GoTrue" target="_blank" rel="noopener noreferrer">gotrue-kt</a></td>
    <td><a href="https://github.com/supabase-community/supabase-kt/tree/master/Realtime" target="_blank" rel="noopener noreferrer">realtime-kt</a></td>
    <td><a href="https://github.com/supabase-community/supabase-kt/tree/master/Storage" target="_blank" rel="noopener noreferrer">storage-kt</a></td>
    <td><a href="https://github.com/supabase-community/supabase-kt/tree/master/Functions" target="_blank" rel="noopener noreferrer">functions-kt</a></td>
  </tr>
  <tr>
    <td>Python</td>
    <td><a href="https://github.com/supabase-community/supabase-py" target="_blank" rel="noopener noreferrer">supabase-py</a></td>
    <td><a href="https://github.com/supabase-community/postgrest-py" target="_blank" rel="noopener noreferrer">postgrest-py</a></td>
    <td><a href="https://github.com/supabase-community/gotrue-py" target="_blank" rel="noopener noreferrer">gotrue-py</a></td>
    <td><a href="https://github.com/supabase-community/realtime-py" target="_blank" rel="noopener noreferrer">realtime-py</a></td>
    <td><a href="https://github.com/supabase-community/storage-py" target="_blank" rel="noopener noreferrer">storage-py</a></td>
    <td><a href="https://github.com/supabase-community/functions-py" target="_blank" rel="noopener noreferrer">functions-py</a></td>
  </tr>
  <tr>
    <td>Ruby</td>
    <td><a href="https://github.com/supabase-community/supabase-rb" target="_blank" rel="noopener noreferrer">supabase-rb</a></td>
    <td><a href="https://github.com/supabase-community/postgrest-rb" target="_blank" rel="noopener noreferrer">postgrest-rb</a></td>
    <td>-</td>
    <td>-</td>
    <td>-</td>
    <td>-</td>
  </tr>
  <tr>
    <td>Rust</td>
    <td>-</td>
    <td><a href="https://github.com/supabase-community/postgrest-rs" target="_blank" rel="noopener noreferrer">postgrest-rs</a></td>
    <td>-</td>
    <td>-</td>
    <td>-</td>
    <td>-</td>
  </tr>
  <tr>
    <td>Swift</td>
    <td><a href="https://github.com/supabase-community/supabase-swift" target="_blank" rel="noopener noreferrer">supabase-swift</a></td>
    <td><a href="https://github.com/supabase-community/postgrest-swift" target="_blank" rel="noopener noreferrer">postgrest-swift</a></td>
    <td><a href="https://github.com/supabase-community/gotrue-swift" target="_blank" rel="noopener noreferrer">gotrue-swift</a></td>
    <td><a href="https://github.com/supabase-community/realtime-swift" target="_blank" rel="noopener noreferrer">realtime-swift</a></td>
    <td><a href="https://github.com/supabase-community/storage-swift" target="_blank" rel="noopener noreferrer">storage-swift</a></td>
    <td><a href="https://github.com/supabase-community/functions-swift" target="_blank" rel="noopener noreferrer">functions-swift</a></td>
  </tr>
  <tr>
    <td>Godot Engine (GDScript)</td>
    <td><a href="https://github.com/supabase-community/godot-engine.supabase" target="_blank" rel="noopener noreferrer">supabase-gdscript</a></td>
    <td><a href="https://github.com/supabase-community/postgrest-gdscript" target="_blank" rel="noopener noreferrer">postgrest-gdscript</a></td>
    <td><a href="https://github.com/supabase-community/gotrue-gdscript" target="_blank" rel="noopener noreferrer">gotrue-gdscript</a></td>
    <td><a href="https://github.com/supabase-community/realtime-gdscript" target="_blank" rel="noopener noreferrer">realtime-gdscript</a></td>
    <td><a href="https://github.com/supabase-community/storage-gdscript" target="_blank" rel="noopener noreferrer">storage-gdscript</a></td>
    <td><a href="https://github.com/supabase-community/functions-gdscript" target="_blank" rel="noopener noreferrer">functions-gdscript</a></td>
  </tr>
  
</table>

<!--- Remove this list if you're translating to another language, it's hard to keep updated across multiple files-->
<!--- Keep only the link to the list of translation files-->

## Μεταφράσεις

- [Αραβικά | العربية](/i18n/README.ar.md)
- [Αλβανικά / Shqip](/i18n/README.sq.md)
- [Bangla / বাংলা](/i18n/README.bn.md)
- [Βουλγαρικά / Български](/i18n/README.bg.md)
- [Καταλανικά / Català](/i18n/README.ca.md)
- [Δανικά / Dansk](/i18n/README.da.md)
- [Ολλανδικά / Nederlands](/i18n/README.nl.md)
- [Αγγλικά](https://github.com/supabase/supabase)
- [Φινλανδικά / Suomalainen](/i18n/README.fi.md)
- [Γαλλικά / Français](/i18n/README.fr.md)
- [Γερμανικά / Deutsch](/i18n/README.de.md)
- [Ελληνικά](/i18n/README.gr.md)
- [Εβραϊκά / עברית](/i18n/README.he.md)
- [Hindi / हिंदी](/i18n/README.hi.md)
- [Ουγγρικά / Magyar](/i18n/README.hu.md)
- [Νεπαλί / नेपाली](/i18n/README.ne.md)
- [Ινδονησιακά / Bahasa Indonesia](/i18n/README.id.md)
- [Ιταλικά / Italiano](/i18n/README.it.md)
- [Ιαπωνικά / 日本語](/i18n/README.jp.md)
- [Κορεάτικα / 한국어](/i18n/README.ko.md)
- [Μαλαισία / Bahasa Malaysia](/i18n/README.ms.md)
- [Νορβηγικά (Bokmål) / Norsk (Bokmål)](/i18n/README.nb-no.md)
- [Περσικά / فارسی](/i18n/README.fa.md)
- [Πολωνικά / Polski](/i18n/README.pl.md)
- [Πορτογαλικά / Português](/i18n/README.pt.md)
- [Πορτογαλικά (Βραζιλία) / Português Brasileiro](/i18n/README.pt-br.md)
- [Ρουμανικά / Română](/i18n/README.ro.md)
- [Ρωσικά / Pусский](/i18n/README.ru.md)
- [Σερβικά / Srpski](/i18n/README.sr.md)
- [Sinhala / සිංහල](/i18n/README.si.md)
- [Ισπανικά / Español](/i18n/README.es.md)
- [Απλοποιημένα Κινέζικα / 简体中文](/i18n/README.zh-cn.md)
- [Σουηδικά / Svenska](/i18n/README.sv.md)
- [Thai / ไทย](/i18n/README.th.md)
- [Παραδοσιακά κινέζικα / 繁體中文](/i18n/README.zh-tw.md)
- [Τουρκικά / Türkçe](/i18n/README.tr.md)
- [Ουκρανικά / Українська](/i18n/README.uk.md)
- [Βιετναμέζικα / Tiếng Việt](/i18n/README.vi-vn.md)
- [Κατάλογος μεταφράσεων](/i18n/languages.md) <!--- Keep only this -->

---

## Χορηγοί

[![Νέος Χορηγός](https://user-images.githubusercontent.com/10214025/90518111-e74bbb00-e198-11ea-8f88-c9e3c1aa4b5b.png)](https://github.com/sponsors/supabase)


## Links discovered
- [Supabase](https://supabase.com)
- [Docs](https://supabase.com/docs/guides/database)
- [Έγγραφα](https://supabase.com/docs/guides/auth)
- [Έγγραφα](https://supabase.com/docs/guides/api#rest-api-overview)
- [Docs](https://supabase.com/docs/guides/api#graphql-api-overview)
- [Έγγραφα](https://supabase.com/docs/guides/api#realtime-api-overview)
- [Docs](https://supabase.com/docs/guides/database/functions)
- [Docs](https://supabase.com/docs/guides/functions)
- [Docs](https://supabase.com/docs/guides/storage)
- [Supabase Dashboard](https://raw.githubusercontent.com/supabase/supabase/master/apps/www/public/images/github/supabase-dashboard.png)
- [supabase.com/docs](https://supabase.com/docs)
- [Getting Started](https://github.com/supabase/supabase/blob/master/DEVELOPERS.md)
- [Community Forum](https://github.com/supabase/supabase/discussions)
- [GitHub Issues](https://github.com/supabase/supabase/issues)
- [Email Support](https://supabase.com/docs/support#business-support)
- [Discord](https://discord.supabase.com)
- [supabase.com/dashboard](https://supabase.com/dashboard)
- [[status](https://supabase.com/docs/guides/getting-started/features#feature-status)
- [φιλοξενούμενη πλατφόρμα](https://supabase.com/dashboard)
- [αυτο-ξενάγηση](https://supabase.com/docs/guides/hosting/overview)
- [ανάπτυξη τοπικά](https://supabase.com/docs/guides/local-development)
- [Αρχιτεκτονική](https://github.com/supabase/supabase/blob/master/apps/docs/public/img/supabase-architecture.svg)
- [PostgreSQL](https://www.postgresql.org/)
- [Realtime](https://github.com/supabase/realtime)
- [PostgREST](http://postgrest.org/)
- [pg_graphql](http://github.com/supabase/pg_graphql/)
- [Storage](https://github.com/supabase/storage-api)
- [postgres-meta](https://github.com/supabase/postgres-meta)
- [GoTrue](https://github.com/netlify/gotrue)
- [Kong](https://github.com/Kong/kong)
- [Αραβικά | العربية](https://github.com/supabase/supabase/blob/master/i18n/README.ar.md)
- [Αλβανικά / Shqip](https://github.com/supabase/supabase/blob/master/i18n/README.sq.md)
- [Bangla / বাংলা](https://github.com/supabase/supabase/blob/master/i18n/README.bn.md)
- [Βουλγαρικά / Български](https://github.com/supabase/supabase/blob/master/i18n/README.bg.md)
- [Καταλανικά / Català](https://github.com/supabase/supabase/blob/master/i18n/README.ca.md)
- [Δανικά / Dansk](https://github.com/supabase/supabase/blob/master/i18n/README.da.md)
- [Ολλανδικά / Nederlands](https://github.com/supabase/supabase/blob/master/i18n/README.nl.md)
- [Αγγλικά](https://github.com/supabase/supabase)
- [Φινλανδικά / Suomalainen](https://github.com/supabase/supabase/blob/master/i18n/README.fi.md)
- [Γαλλικά / Français](https://github.com/supabase/supabase/blob/master/i18n/README.fr.md)
- [Γερμανικά / Deutsch](https://github.com/supabase/supabase/blob/master/i18n/README.de.md)
- [Ελληνικά](https://github.com/supabase/supabase/blob/master/i18n/README.gr.md)
- [Εβραϊκά / עברית](https://github.com/supabase/supabase/blob/master/i18n/README.he.md)
- [Hindi / हिंदी](https://github.com/supabase/supabase/blob/master/i18n/README.hi.md)
- [Ουγγρικά / Magyar](https://github.com/supabase/supabase/blob/master/i18n/README.hu.md)
- [Νεπαλί / नेपाली](https://github.com/supabase/supabase/blob/master/i18n/README.ne.md)
- [Ινδονησιακά / Bahasa Indonesia](https://github.com/supabase/supabase/blob/master/i18n/README.id.md)
- [Ιταλικά / Italiano](https://github.com/supabase/supabase/blob/master/i18n/README.it.md)
- [Ιαπωνικά / 日本語](https://github.com/supabase/supabase/blob/master/i18n/README.jp.md)
- [Κορεάτικα / 한국어](https://github.com/supabase/supabase/blob/master/i18n/README.ko.md)
- [Μαλαισία / Bahasa Malaysia](https://github.com/supabase/supabase/blob/master/i18n/README.ms.md)
- [Νορβηγικά (Bokmål) / Norsk (Bokmål)](https://github.com/supabase/supabase/blob/master/i18n/README.nb-no.md)
- [Περσικά / فارسی](https://github.com/supabase/supabase/blob/master/i18n/README.fa.md)
- [Πολωνικά / Polski](https://github.com/supabase/supabase/blob/master/i18n/README.pl.md)
- [Πορτογαλικά / Português](https://github.com/supabase/supabase/blob/master/i18n/README.pt.md)
- [Πορτογαλικά (Βραζιλία) / Português Brasileiro](https://github.com/supabase/supabase/blob/master/i18n/README.pt-br.md)
- [Ρουμανικά / Română](https://github.com/supabase/supabase/blob/master/i18n/README.ro.md)
- [Ρωσικά / Pусский](https://github.com/supabase/supabase/blob/master/i18n/README.ru.md)
- [Σερβικά / Srpski](https://github.com/supabase/supabase/blob/master/i18n/README.sr.md)
- [Sinhala / සිංහල](https://github.com/supabase/supabase/blob/master/i18n/README.si.md)
- [Ισπανικά / Español](https://github.com/supabase/supabase/blob/master/i18n/README.es.md)
- [Απλοποιημένα Κινέζικα / 简体中文](https://github.com/supabase/supabase/blob/master/i18n/README.zh-cn.md)
- [Σουηδικά / Svenska](https://github.com/supabase/supabase/blob/master/i18n/README.sv.md)
- [Thai / ไทย](https://github.com/supabase/supabase/blob/master/i18n/README.th.md)
- [Παραδοσιακά κινέζικα / 繁體中文](https://github.com/supabase/supabase/blob/master/i18n/README.zh-tw.md)
- [Τουρκικά / Türkçe](https://github.com/supabase/supabase/blob/master/i18n/README.tr.md)
- [Ουκρανικά / Українська](https://github.com/supabase/supabase/blob/master/i18n/README.uk.md)
- [Βιετναμέζικα / Tiếng Việt](https://github.com/supabase/supabase/blob/master/i18n/README.vi-vn.md)
- [Κατάλογος μεταφράσεων](https://github.com/supabase/supabase/blob/master/i18n/languages.md)
- [![Νέος Χορηγός](https://user-images.githubusercontent.com/10214025/90518111-e74bbb00-e198-11ea-8f88-c9e3c1aa4b5b.png)
- [PostgREST](https://github.com/postgrest/postgrest)
- [GoTrue](https://github.com/supabase/gotrue)
- [supabase-lang](https://github.com/supabase-community/supabase-lang)
- [postgrest-lang](https://github.com/supabase-community/postgrest-lang)
- [gotrue-lang](https://github.com/supabase-community/gotrue-lang)
- [realtime-lang](https://github.com/supabase-community/realtime-lang)
- [storage-lang](https://github.com/supabase-community/storage-lang)
- [supabase-js](https://github.com/supabase/supabase-js)
- [postgrest-js](https://github.com/supabase/supabase-js/tree/master/packages/core/postgrest-js)
- [auth-js](https://github.com/supabase/supabase-js/tree/master/packages/core/auth-js)
- [realtime-js](https://github.com/supabase/supabase-js/tree/master/packages/core/realtime-js)
- [storage-js](https://github.com/supabase/supabase-js/tree/master/packages/core/storage-js)
- [functions-js](https://github.com/supabase/supabase-js/tree/master/packages/core/functions-js)
- [supabase-flutter](https://github.com/supabase/supabase-flutter)
- [postgrest-dart](https://github.com/supabase/postgrest-dart)
- [gotrue-dart](https://github.com/supabase/gotrue-dart)
- [realtime-dart](https://github.com/supabase/realtime-dart)
- [storage-dart](https://github.com/supabase/storage-dart)
- [functions-dart](https://github.com/supabase/functions-dart)
- [supabase-csharp](https://github.com/supabase-community/supabase-csharp)
- [postgrest-csharp](https://github.com/supabase-community/postgrest-csharp)
- [gotrue-csharp](https://github.com/supabase-community/gotrue-csharp)
- [realtime-csharp](https://github.com/supabase-community/realtime-csharp)
- [storage-csharp](https://github.com/supabase-community/storage-csharp)
- [functions-csharp](https://github.com/supabase-community/functions-csharp)
- [postgrest-go](https://github.com/supabase-community/postgrest-go)
- [gotrue-go](https://github.com/supabase-community/gotrue-go)
- [storage-go](https://github.com/supabase-community/storage-go)
- [functions-go](https://github.com/supabase-community/functions-go)
- [gotrue-java](https://github.com/supabase-community/gotrue-java)

--- i18n/README.es.md ---
<p align="center">
<img src="https://user-images.githubusercontent.com/8291514/213727234-cda046d6-28c6-491a-b284-b86c5cede25d.png#gh-light-mode-only">
<img src="https://user-images.githubusercontent.com/8291514/213727225-56186826-bee8-43b5-9b15-86e839d89393.png#gh-dark-mode-only">
</p>

---

# Supabase

[Supabase](https://supabase.com) es una alternativa de código abierto a Firebase. Estamos construyendo las características de Firebase utilizando herramientas de código abierto de nivel empresarial.

- [x] Base de datos Postgres alojada. [Documentación](https://supabase.com/docs/guides/database)
- [x] Autenticación y autorización. [Documentos](https://supabase.com/docs/guides/auth)
- [x] API autogeneradas.
  - [x] REST. [Docs](https://supabase.com/docs/guides/api#rest-api-overview)
  - [x] GraphQL. [Documentos](https://supabase.com/docs/guides/api#graphql-api-overview)
  - [x] Suscripciones en tiempo real. [Documentos](https://supabase.com/docs/guides/api#realtime-api-overview)
- [x] Funciones.
  - [x] Funciones de base de datos. [Docs](https://supabase.com/docs/guides/database/functions)
  - [x] Funciones de borde [Docs](https://supabase.com/docs/guides/functions)
- [x] Almacenamiento de archivos. [Documentos](https://supabase.com/docs/guides/storage)
- [x] Panel de control

[Supabase Dashboard](https://raw.githubusercontent.com/supabase/supabase/master/apps/www/public/images/github/supabase-dashboard.png)

## Documentación

Para consultar la documentación completa, visite [supabase.com/docs](https://supabase.com/docs)

Para ver cómo contribuir, visite [Getting Started](../DEVELOPERS.md)

## Comunidad y soporte

- [Foro de la comunidad](https://github.com/supabase/supabase/discussions). Lo mejor para: ayuda con la construcción, discusión sobre las mejores prácticas de bases de datos.
- [Problemas en GitHub](https://github.com/supabase/supabase/issues). Lo mejor para: bugs y errores que encuentres usando Supabase.
- [Soporte por correo electrónico](https://supabase.com/docs/support#business-support). Lo mejor para: problemas con tu base de datos o infraestructura.
- [Discord](https://discord.supabase.com). Lo mejor para: compartir tus aplicaciones y pasar el rato con la comunidad.

## Estado

- [x] Alfa: Estamos probando Supabase con un grupo cerrado de clientes
- [x] Alfa público: Cualquiera puede registrarse en [supabase.com/dashboard](https://supabase.com/dashboard). Pero no te pases, hay algunos problemas
- [Beta pública: Suficientemente estable para la mayoría de los casos de uso no empresariales
- [Público: Disponibilidad general [[status](https://supabase.com/docs/guides/getting-started/features#feature-status)]

Actualmente estamos en Beta Pública. Esté atento a "releases" de este repositorio para recibir notificaciones de actualizaciones importantes.

<kbd><img src="https://raw.githubusercontent.com/supabase/supabase/d5f7f413ab356dc1a92075cb3cee4e40a957d5b1/web/static/watch-repo.gif" alt="Watch this repo"/></kbd>

---

## Cómo funciona

Supabase es una combinación de herramientas de código abierto. Estamos construyendo las características de Firebase utilizando productos de código abierto de nivel empresarial. Si las herramientas y las comunidades existen, con una licencia abierta MIT, Apache 2 o equivalente, utilizaremos y daremos soporte a esa herramienta. Si la herramienta no existe, la construimos y la desarrollamos nosotros mismos. Supabase no es un mapeo 1 a 1 de Firebase. Nuestro objetivo es ofrecer a los desarrolladores una experiencia similar a la de Firebase utilizando herramientas de código abierto.

**Arquitectura**

Supabase es una [plataforma alojada](https://supabase.com/dashboard). Puedes registrarte y empezar a usar Supabase sin instalar nada.
También puede [autoalojarse](https://supabase.com/docs/guides/hosting/overview) y [desarrollar localmente](https://supabase.com/docs/guides/local-development).

![arquitectura](https://github.com/supabase/supabase/blob/master/apps/docs/public/img/supabase-architecture.svg)

- [PostgreSQL](https://www.postgresql.org/) es un sistema de base de datos objeto-relacional con más de 30 años de desarrollo activo que le ha valido una sólida reputación por su fiabilidad, robustez de características y rendimiento.
- [Realtime](https://github.com/supabase/realtime) es un servidor Elixir que te permite escuchar las inserciones, actualizaciones y eliminaciones de PostgreSQL utilizando websockets. Realtime sondea la funcionalidad de replicación integrada de Postgres en busca de cambios en la base de datos, convierte los cambios a JSON y, a continuación, transmite el JSON a través de websockets a los clientes autorizados.
- [PostgREST](http://postgrest.org/) es un servidor web que convierte su base de datos PostgreSQL directamente en una API RESTful
- [pg_graphql](http://github.com/supabase/pg_graphql/) una extensión de PostgreSQL que expone una API GraphQL
- [Storage](https://github.com/supabase/storage-api) proporciona una interfaz RESTful para gestionar archivos almacenados en S3, usando Postgres para gestionar permisos.
- [postgres-meta](https://github.com/supabase/postgres-meta) es una API RESTful para gestionar tu Postgres, permitiéndote obtener tablas, añadir roles, ejecutar consultas, etc.
- [GoTrue](https://github.com/netlify/gotrue) es una API basada en SWT para gestionar usuarios y emitir tokens SWT.
- [Kong](https://github.com/Kong/kong) es una pasarela API nativa en la nube.

#### Bibliotecas de cliente

Nuestro enfoque para las bibliotecas cliente es modular. Cada sublibrería es una implementación independiente para un único sistema externo. Esta es una de las formas en que apoyamos las herramientas existentes.

<table style="table-layout:fixed; white-space: nowrap;">
  <tr>
    <th>Idioma</th>
    <th>Cliente</th>
    <th colspan="5">Feature-Clients (incluido en el cliente Supabase)</th>
  </tr>
  
  <tr>
    <th></th>
    <th>Supabase</th>
    <th><a href="https://github.com/postgrest/postgrest" target="_blank" rel="noopener noreferrer">PostgREST</a></th>
    <th><a href="https://github.com/supabase/gotrue" target="_blank" rel="noopener noreferrer">GoTrue</a></th>
    <th><a href="https://github.com/supabase/realtime" target="_blank" rel="noopener noreferrer">Realtime</a></th>
    <th><a href="https://github.com/supabase/storage-api" target="_blank" rel="noopener noreferrer">Storage</a></th>
    <th>Functions</th>
  </tr>
  <!-- TEMPLATE FOR NEW ROW -->
  <!-- START ROW
  <tr>
    <td>lang</td>
    <td><a href="https://github.com/supabase-community/supabase-lang" target="_blank" rel="noopener noreferrer">supabase-lang</a></td>
    <td><a href="https://github.com/supabase-community/postgrest-lang" target="_blank" rel="noopener noreferrer">postgrest-lang</a></td>
    <td><a href="https://github.com/supabase-community/gotrue-lang" target="_blank" rel="noopener noreferrer">gotrue-lang</a></td>
    <td><a href="https://github.com/supabase-community/realtime-lang" target="_blank" rel="noopener noreferrer">realtime-lang</a></td>
    <td><a href="https://github.com/supabase-community/storage-lang" target="_blank" rel="noopener noreferrer">storage-lang</a></td>
  </tr>
  END ROW -->
  
  <th colspan="7">⚡️ Oficial ⚡️</th>
  
  <tr>
    <td>JavaScript (TypeScript)</td>
    <td><a href="https://github.com/supabase/supabase-js" target="_blank" rel="noopener noreferrer">supabase-js</a></td>
    <td><a href="https://github.com/supabase/supabase-js/tree/master/packages/core/postgrest-js" target="_blank" rel="noopener noreferrer">postgrest-js</a></td>
    <td><a href="https://github.com/supabase/supabase-js/tree/master/packages/core/auth-js" target="_blank" rel="noopener noreferrer">auth-js</a></td>
    <td><a href="https://github.com/supabase/supabase-js/tree/master/packages/core/realtime-js" target="_blank" rel="noopener noreferrer">realtime-js</a></td>
    <td><a href="https://github.com/supabase/supabase-js/tree/master/packages/core/storage-js" target="_blank" rel="noopener noreferrer">storage-js</a></td>
    <td><a href="https://github.com/supabase/supabase-js/tree/master/packages/core/functions-js" target="_blank" rel="noopener noreferrer">functions-js</a></td>
  </tr>
    <tr>
    <td>Flutter</td>
    <td><a href="https://github.com/supabase/supabase-flutter" target="_blank" rel="noopener noreferrer">supabase-flutter</a></td>
    <td><a href="https://github.com/supabase/postgrest-dart" target="_blank" rel="noopener noreferrer">postgrest-dart</a></td>
    <td><a href="https://github.com/supabase/gotrue-dart" target="_blank" rel="noopener noreferrer">gotrue-dart</a></td>
    <td><a href="https://github.com/supabase/realtime-dart" target="_blank" rel="noopener noreferrer">realtime-dart</a></td>
    <td><a href="https://github.com/supabase/storage-dart" target="_blank" rel="noopener noreferrer">storage-dart</a></td>
    <td><a href="https://github.com/supabase/functions-dart" target="_blank" rel="noopener noreferrer">functions-dart</a></td>
  </tr>
  
  <th colspan="7">💚 Comunidad 💚</th>
  
  <tr>
    <td>C#</td>
    <td><a href="https://github.com/supabase-community/supabase-csharp" target="_blank" rel="noopener noreferrer">supabase-csharp</a></td>
    <td><a href="https://github.com/supabase-community/postgrest-csharp" target="_blank" rel="noopener noreferrer">postgrest-csharp</a></td>
    <td><a href="https://github.com/supabase-community/gotrue-csharp" target="_blank" rel="noopener noreferrer">gotrue-csharp</a></td>
    <td><a href="https://github.com/supabase-community/realtime-csharp" target="_blank" rel="noopener noreferrer">realtime-csharp</a></td>
    <td><a href="https://github.com/supabase-community/storage-csharp" target="_blank" rel="noopener noreferrer">storage-csharp</a></td>
    <td><a href="https://github.com/supabase-community/functions-csharp" target="_blank" rel="noopener noreferrer">functions-csharp</a></td>
  </tr>
  <tr>
    <td>Go</td>
    <td>-</td>
    <td><a href="https://github.com/supabase-community/postgrest-go" target="_blank" rel="noopener noreferrer">postgrest-go</a></td>
    <td><a href="https://github.com/supabase-community/gotrue-go" target="_blank" rel="noopener noreferrer">gotrue-go</a></td>
    <td>-</td>
    <td><a href="https://github.com/supabase-community/storage-go" target="_blank" rel="noopener noreferrer">storage-go</a></td>
    <td><a href="https://github.com/supabase-community/functions-go" target="_blank" rel="noopener noreferrer">functions-go</a></td>
  </tr>
  <tr>
    <td>Java</td>
    <td>-</td>
    <td>-</td>
    <td><a href="https://github.com/supabase-community/gotrue-java" target="_blank" rel="noopener noreferrer">gotrue-java</a></td>
    <td>-</td>
    <td><a href="https://github.com/supabase-community/storage-java" target="_blank" rel="noopener noreferrer">storage-java</a></td>
    <td>-</td>
  </tr>
  <tr>
    <td>Kotlin</td>
    <td><a href="https://github.com/supabase-community/supabase-kt" target="_blank" rel="noopener noreferrer">supabase-kt</a></td>
    <td><a href="https://github.com/supabase-community/supabase-kt/tree/master/Postgrest" target="_blank" rel="noopener noreferrer">postgrest-kt</a></td>
    <td><a href="https://github.com/supabase-community/supabase-kt/tree/master/GoTrue" target="_blank" rel="noopener noreferrer">gotrue-kt</a></td>
    <td><a href="https://github.com/supabase-community/supabase-kt/tree/master/Realtime" target="_blank" rel="noopener noreferrer">realtime-kt</a></td>
    <td><a href="https://github.com/supabase-community/supabase-kt/tree/master/Storage" target="_blank" rel="noopener noreferrer">storage-kt</a></td>
    <td><a href="https://github.com/supabase-community/supabase-kt/tree/master/Functions" target="_blank" rel="noopener noreferrer">functions-kt</a></td>
  </tr>
  <tr>
    <td>Python</td>
    <td><a href="https://github.com/supabase-community/supabase-py" target="_blank" rel="noopener noreferrer">supabase-py</a></td>
    <td><a href="https://github.com/supabase-community/postgrest-py" target="_blank" rel="noopener noreferrer">postgrest-py</a></td>
    <td><a href="https://github.com/supabase-community/gotrue-py" target="_blank" rel="noopener noreferrer">gotrue-py</a></td>
    <td><a href="https://github.com/supabase-community/realtime-py" target="_blank" rel="noopener noreferrer">realtime-py</a></td>
    <td><a href="https://github.com/supabase-community/storage-py" target="_blank" rel="noopener noreferrer">storage-py</a></td>
    <td><a href="https://github.com/supabase-community/functions-py" target="_blank" rel="noopener noreferrer">functions-py</a></td>
  </tr>
  <tr>
    <td>Ruby</td>
    <td><a href="https://github.com/supabase-community/supabase-rb" target="_blank" rel="noopener noreferrer">supabase-rb</a></td>
    <td><a href="https://github.com/supabase-community/postgrest-rb" target="_blank" rel="noopener noreferrer">postgrest-rb</a></td>
    <td>-</td>
    <td>-</td>
    <td>-</td>
    <td>-</td>
  </tr>
  <tr>
    <td>Rust</td>
    <td>-</td>
    <td><a href="https://github.com/supabase-community/postgrest-rs" target="_blank" rel="noopener noreferrer">postgrest-rs</a></td>
    <td>-</td>
    <td>-</td>
    <td>-</td>
    <td>-</td>
  </tr>
  <tr>
    <td>Swift</td>
    <td><a href="https://github.com/supabase-community/supabase-swift" target="_blank" rel="noopener noreferrer">supabase-swift</a></td>
    <td><a href="https://github.com/supabase-community/postgrest-swift" target="_blank" rel="noopener noreferrer">postgrest-swift</a></td>
    <td><a href="https://github.com/supabase-community/gotrue-swift" target="_blank" rel="noopener noreferrer">gotrue-swift</a></td>
    <td><a href="https://github.com/supabase-community/realtime-swift" target="_blank" rel="noopener noreferrer">realtime-swift</a></td>
    <td><a href="https://github.com/supabase-community/storage-swift" target="_blank" rel="noopener noreferrer">storage-swift</a></td>
    <td><a href="https://github.com/supabase-community/functions-swift" target="_blank" rel="noopener noreferrer">functions-swift</a></td>
  </tr>
  <tr>
    <td>Godot Engine (GDScript)</td>
    <td><a href="https://github.com/supabase-community/godot-engine.supabase" target="_blank" rel="noopener noreferrer">supabase-gdscript</a></td>
    <td><a href="https://github.com/supabase-community/postgrest-gdscript" target="_blank" rel="noopener noreferrer">postgrest-gdscript</a></td>
    <td><a href="https://github.com/supabase-community/gotrue-gdscript" target="_blank" rel="noopener noreferrer">gotrue-gdscript</a></td>
    <td><a href="https://github.com/supabase-community/realtime-gdscript" target="_blank" rel="noopener noreferrer">realtime-gdscript</a></td>
    <td><a href="https://github.com/supabase-community/storage-gdscript" target="_blank" rel="noopener noreferrer">storage-gdscript</a></td>
    <td><a href="https://github.com/supabase-community/functions-gdscript" target="_blank" rel="noopener noreferrer">functions-gdscript</a></td>
  </tr>
  
</table>

<!--- Remove this list if you're translating to another language, it's hard to keep updated across multiple files-->
<!--- Keep only the link to the list of translation files-->

## Traducciones

- [Árabe | العربية](/i18n/README.ar.md)
- [albanés / shqip](/i18n/README.sq.md)
- [Bangla / বাংলা](/i18n/README.bn.md)
- [Búlgaro / Български](/i18n/README.bg.md)
- [Catalán / Català](/i18n/README.ca.md)
- [Danés / Dansk](/i18n/README.da.md)
- [Holandés / Nederlands](/i18n/README.nl.md)
- [Inglés](https://github.com/supabase/supabase)
- [Finlandés / Suomalainen](/i18n/README.fi.md)
- [Francés / Français](/i18n/README.fr.md)
- [Alemán / Deutsch](/i18n/README.de.md)
- [Griego / Ελληνικά](/i18n/README.gr.md)
- [Hebreo / עברית](/i18n/README.he.md)
- [Hindi / हिंदी](/i18n/README.hi.md)
- [Húngaro / Magyar](/i18n/README.hu.md)
- [Nepalí / नेपाली](/i18n/README.ne.md)
- [Indonesio / Bahasa Indonesia](/i18n/README.id.md)
- [Italiano / Italiano](/i18n/README.it.md)
- [Japonés / 日本語](/i18n/README.jp.md)
- [Coreano / 한국어](/i18n/README.ko.md)
- [Malayo / Bahasa Malaysia](/i18n/README.ms.md)
- [Noruego (Bokmål) / Norsk (Bokmål)](/i18n/README.nb-no.md)
- [Persa / فارسی](/i18n/README.fa.md)
- [Polaco / Polski](/i18n/README.pl.md)
- [Portugués / Português](/i18n/README.pt.md)
- [Portugués (brasileño) / Português Brasileiro](/i18n/README.pt-br.md)
- [Rumano / Română](/i18n/README.ro.md)
- [Ruso / Pусский](/i18n/README.ru.md)
- [Serbio / Srpski](/i18n/README.sr.md)
- [Sinhala / සිංහල](/i18n/README.si.md)
- [Español / English](/i18n/README.es.md)
- [Chino simplificado / 简体中文](/i18n/README.zh-cn.md)
- [Sueco / Svenska](/i18n/README.sv.md)
- [Tailandés / ไทย](/i18n/README.th.md)
- [Chino tradicional / 繁體中文](/i18n/README.zh-tw.md)
- [Turco / Türkçe](/i18n/README.tr.md)
- [Ucraniano / Українська](/i18n/README.uk.md)
- [Vietnamita / Tiếng Việt](/i18n/README.vi-vn.md)
- [Lista de traducciones](/i18n/languages.md) <!--- Keep only this -->

---

## Patrocinadores

[![Nuevo Patrocinador](https://user-images.githubusercontent.com/10214025/90518111-e74bbb00-e198-11ea-8f88-c9e3c1aa4b5b.png)](https://github.com/sponsors/supabase)


## Links discovered
- [Supabase](https://supabase.com)
- [Documentación](https://supabase.com/docs/guides/database)
- [Documentos](https://supabase.com/docs/guides/auth)
- [Docs](https://supabase.com/docs/guides/api#rest-api-overview)
- [Documentos](https://supabase.com/docs/guides/api#graphql-api-overview)
- [Documentos](https://supabase.com/docs/guides/api#realtime-api-overview)
- [Docs](https://supabase.com/docs/guides/database/functions)
- [Docs](https://supabase.com/docs/guides/functions)
- [Documentos](https://supabase.com/docs/guides/storage)
- [Supabase Dashboard](https://raw.githubusercontent.com/supabase/supabase/master/apps/www/public/images/github/supabase-dashboard.png)
- [supabase.com/docs](https://supabase.com/docs)
- [Getting Started](https://github.com/supabase/supabase/blob/master/DEVELOPERS.md)
- [Foro de la comunidad](https://github.com/supabase/supabase/discussions)
- [Problemas en GitHub](https://github.com/supabase/supabase/issues)
- [Soporte por correo electrónico](https://supabase.com/docs/support#business-support)
- [Discord](https://discord.supabase.com)
- [supabase.com/dashboard](https://supabase.com/dashboard)
- [Beta pública: Suficientemente estable para la mayoría de los casos de uso no empresariales
- [Público: Disponibilidad general [[status](https://supabase.com/docs/guides/getting-started/features#feature-status)
- [plataforma alojada](https://supabase.com/dashboard)
- [autoalojarse](https://supabase.com/docs/guides/hosting/overview)
- [desarrollar localmente](https://supabase.com/docs/guides/local-development)
- [arquitectura](https://github.com/supabase/supabase/blob/master/apps/docs/public/img/supabase-architecture.svg)
- [PostgreSQL](https://www.postgresql.org/)
- [Realtime](https://github.com/supabase/realtime)
- [PostgREST](http://postgrest.org/)
- [pg_graphql](http://github.com/supabase/pg_graphql/)
- [Storage](https://github.com/supabase/storage-api)
- [postgres-meta](https://github.com/supabase/postgres-meta)
- [GoTrue](https://github.com/netlify/gotrue)
- [Kong](https://github.com/Kong/kong)
- [Árabe | العربية](https://github.com/supabase/supabase/blob/master/i18n/README.ar.md)
- [albanés / shqip](https://github.com/supabase/supabase/blob/master/i18n/README.sq.md)
- [Bangla / বাংলা](https://github.com/supabase/supabase/blob/master/i18n/README.bn.md)
- [Búlgaro / Български](https://github.com/supabase/supabase/blob/master/i18n/README.bg.md)
- [Catalán / Català](https://github.com/supabase/supabase/blob/master/i18n/README.ca.md)
- [Danés / Dansk](https://github.com/supabase/supabase/blob/master/i18n/README.da.md)
- [Holandés / Nederlands](https://github.com/supabase/supabase/blob/master/i18n/README.nl.md)
- [Inglés](https://github.com/supabase/supabase)
- [Finlandés / Suomalainen](https://github.com/supabase/supabase/blob/master/i18n/README.fi.md)
- [Francés / Français](https://github.com/supabase/supabase/blob/master/i18n/README.fr.md)
- [Alemán / Deutsch](https://github.com/supabase/supabase/blob/master/i18n/README.de.md)
- [Griego / Ελληνικά](https://github.com/supabase/supabase/blob/master/i18n/README.gr.md)
- [Hebreo / עברית](https://github.com/supabase/supabase/blob/master/i18n/README.he.md)
- [Hindi / हिंदी](https://github.com/supabase/supabase/blob/master/i18n/README.hi.md)
- [Húngaro / Magyar](https://github.com/supabase/supabase/blob/master/i18n/README.hu.md)
- [Nepalí / नेपाली](https://github.com/supabase/supabase/blob/master/i18n/README.ne.md)
- [Indonesio / Bahasa Indonesia](https://github.com/supabase/supabase/blob/master/i18n/README.id.md)
- [Italiano / Italiano](https://github.com/supabase/supabase/blob/master/i18n/README.it.md)
- [Japonés / 日本語](https://github.com/supabase/supabase/blob/master/i18n/README.jp.md)
- [Coreano / 한국어](https://github.com/supabase/supabase/blob/master/i18n/README.ko.md)
- [Malayo / Bahasa Malaysia](https://github.com/supabase/supabase/blob/master/i18n/README.ms.md)
- [Noruego (Bokmål) / Norsk (Bokmål)](https://github.com/supabase/supabase/blob/master/i18n/README.nb-no.md)
- [Persa / فارسی](https://github.com/supabase/supabase/blob/master/i18n/README.fa.md)
- [Polaco / Polski](https://github.com/supabase/supabase/blob/master/i18n/README.pl.md)
- [Portugués / Português](https://github.com/supabase/supabase/blob/master/i18n/README.pt.md)
- [Portugués (brasileño) / Português Brasileiro](https://github.com/supabase/supabase/blob/master/i18n/README.pt-br.md)
- [Rumano / Română](https://github.com/supabase/supabase/blob/master/i18n/README.ro.md)
- [Ruso / Pусский](https://github.com/supabase/supabase/blob/master/i18n/README.ru.md)
- [Serbio / Srpski](https://github.com/supabase/supabase/blob/master/i18n/README.sr.md)
- [Sinhala / සිංහල](https://github.com/supabase/supabase/blob/master/i18n/README.si.md)
- [Español / English](https://github.com/supabase/supabase/blob/master/i18n/README.es.md)
- [Chino simplificado / 简体中文](https://github.com/supabase/supabase/blob/master/i18n/README.zh-cn.md)
- [Sueco / Svenska](https://github.com/supabase/supabase/blob/master/i18n/README.sv.md)
- [Tailandés / ไทย](https://github.com/supabase/supabase/blob/master/i18n/README.th.md)
- [Chino tradicional / 繁體中文](https://github.com/supabase/supabase/blob/master/i18n/README.zh-tw.md)
- [Turco / Türkçe](https://github.com/supabase/supabase/blob/master/i18n/README.tr.md)
- [Ucraniano / Українська](https://github.com/supabase/supabase/blob/master/i18n/README.uk.md)
- [Vietnamita / Tiếng Việt](https://github.com/supabase/supabase/blob/master/i18n/README.vi-vn.md)
- [Lista de traducciones](https://github.com/supabase/supabase/blob/master/i18n/languages.md)
- [![Nuevo Patrocinador](https://user-images.githubusercontent.com/10214025/90518111-e74bbb00-e198-11ea-8f88-c9e3c1aa4b5b.png)
- [PostgREST](https://github.com/postgrest/postgrest)
- [GoTrue](https://github.com/supabase/gotrue)
- [supabase-lang](https://github.com/supabase-community/supabase-lang)
- [postgrest-lang](https://github.com/supabase-community/postgrest-lang)
- [gotrue-lang](https://github.com/supabase-community/gotrue-lang)
- [realtime-lang](https://github.com/supabase-community/realtime-lang)
- [storage-lang](https://github.com/supabase-community/storage-lang)
- [supabase-js](https://github.com/supabase/supabase-js)
- [postgrest-js](https://github.com/supabase/supabase-js/tree/master/packages/core/postgrest-js)
- [auth-js](https://github.com/supabase/supabase-js/tree/master/packages/core/auth-js)
- [realtime-js](https://github.com/supabase/supabase-js/tree/master/packages/core/realtime-js)
- [storage-js](https://github.com/supabase/supabase-js/tree/master/packages/core/storage-js)
- [functions-js](https://github.com/supabase/supabase-js/tree/master/packages/core/functions-js)
- [supabase-flutter](https://github.com/supabase/supabase-flutter)
- [postgrest-dart](https://github.com/supabase/postgrest-dart)
- [gotrue-dart](https://github.com/supabase/gotrue-dart)
- [realtime-dart](https://github.com/supabase/realtime-dart)
- [storage-dart](https://github.com/supabase/storage-dart)
- [functions-dart](https://github.com/supabase/functions-dart)
- [supabase-csharp](https://github.com/supabase-community/supabase-csharp)
- [postgrest-csharp](https://github.com/supabase-community/postgrest-csharp)
- [gotrue-csharp](https://github.com/supabase-community/gotrue-csharp)
- [realtime-csharp](https://github.com/supabase-community/realtime-csharp)
- [storage-csharp](https://github.com/supabase-community/storage-csharp)
- [functions-csharp](https://github.com/supabase-community/functions-csharp)
- [postgrest-go](https://github.com/supabase-community/postgrest-go)
- [gotrue-go](https://github.com/supabase-community/gotrue-go)
- [storage-go](https://github.com/supabase-community/storage-go)
- [functions-go](https://github.com/supabase-community/functions-go)
- [gotrue-java](https://github.com/supabase-community/gotrue-java)

--- i18n/README.et.md ---
<p align="center">
<img src="https://user-images.githubusercontent.com/8291514/213727234-cda046d6-28c6-491a-b284-b86c5cede25d.png#gh-light-mode-only">
<img src="https://user-images.githubusercontent.com/8291514/213727225-56186826-bee8-43b5-9b15-86e839d89393.png#gh-dark-mode-only">
</p>

---

# Supabase

[Supabase](https://supabase.com) on avatud lähtekoodiga Firebase'i alternatiiv. Me ehitame Firebase'i funktsioonid, kasutades ettevõtlusklassi avatud lähtekoodiga tööriistu.

- [x] Hostitud Postgres andmebaas. [Dokumendid](https://supabase.com/docs/guides/database)
- [x] Autentimine ja autoriseerimine. [Dokumendid](https://supabase.com/docs/guides/auth)
- [x] Automaatselt genereeritud APId.
  - [x] REST. [Dokumendid](https://supabase.com/docs/guides/api#rest-api-overview)
  - [x] GraphQL. [Dokumendid](https://supabase.com/docs/guides/api#graphql-api-overview)
  - [x] Reaalajas toimivad tellimused. [Dokumendid](https://supabase.com/docs/guides/api#realtime-api-overview)
- [x] Funktsioonid.
  - [x] Andmebaasi funktsioonid. [Dokumendid](https://supabase.com/docs/guides/database/functions)
  - [x] Edge Functions [Docs](https://supabase.com/docs/guides/functions)
- [x] Faili salvestamine. [Dokumendid](https://supabase.com/docs/guides/storage)
- [x] Armatuurlaud

![Supabase Dashboard](https://raw.githubusercontent.com/supabase/supabase/master/apps/www/public/images/github/supabase-dashboard.png)

## Dokumentatsioon

Täieliku dokumentatsiooni saamiseks külastage [supabase.com/docs](https://supabase.com/docs)

Et näha, kuidas panustada, külastage [Getting Started](../DEVELOPERS.md)

## Kogukond ja tugi

- [Ühenduse foorum](https://github.com/supabase/supabase/discussions). Parim: abi ehitamisel, arutelu andmebaasi parimate tavade üle.
- [GitHub Issues](https://github.com/supabase/supabase/issues). Parim lahendus: vead ja vead, millega Supabase'i kasutades kokku puutute.
- [E-posti tugi](https://supabase.com/docs/support#business-support). Parim lahendus: probleemid andmebaasi või infrastruktuuriga.
- [Discord](https://discord.supabase.com). Parim: oma rakenduste jagamiseks ja kogukonnaga suhtlemiseks.

## Staatus

- [x] Alpha: Me testime Supabase'i suletud kliendikogumiga
- [x] Avalik Alpha: Igaüks saab registreeruda aadressil [supabase.com/dashboard](https://supabase.com/dashboard). Kuid olge meiega ettevaatlikud, seal on mõned veidrused
- [x] Avalik beeta: Piisavalt stabiilne enamiku mitte-ettevõtluskasutuse jaoks
- [ ] Avalik: Üldine kättesaadavus [[status](https://supabase.com/docs/guides/getting-started/features#feature-status)]

Oleme praegu Public Beta versioonis. Jälgige selle repo "releases", et saada teateid suuremate uuenduste kohta.

<kbd><img src="https://raw.githubusercontent.com/supabase/supabase/d5f7f413ab356dc1a92075cb3cee4e40a957d5b1/web/static/watch-repo.gif" alt="Watch this repo"/></kbd>

---

## Kuidas see töötab

Supabase on avatud lähtekoodiga tööriistade kombinatsioon. Me ehitame Firebase'i funktsioonid üles, kasutades ettevõtte kvaliteediga avatud lähtekoodiga tooteid. Kui tööriistad ja kogukonnad on olemas MIT, Apache 2 või samaväärse avatud litsentsiga, kasutame ja toetame seda tööriista. Kui tööriista ei ole olemas, siis ehitame selle ise ja kasutame avatud lähtekoodi. Supabase ei ole Firebase'i 1:1 kaardistus. Meie eesmärk on pakkuda arendajatele Firebase'ile sarnast arenduskogemust, kasutades avatud lähtekoodiga tööriistu.

**Arhitektuur**

Supabase on [hostitud platvorm](https://supabase.com/dashboard). Võite registreeruda ja alustada Supabase'i kasutamist ilma midagi installimata.
Võite ka [ise hostida](https://supabase.com/docs/guides/hosting/overview) ja [arendada lokaalselt](https://supabase.com/docs/guides/local-development).

![Arhitektuur](https://github.com/supabase/supabase/blob/master/apps/docs/public/img/supabase-architecture.svg)

- [PostgreSQL](https://www.postgresql.org/) on objekt-relatsiooniline andmebaasisüsteem, mille aktiivne arendamine on kestnud üle 30 aasta ja mis on saavutanud hea maine usaldusväärsuse, funktsioonide töökindluse ja jõudluse poolest.
- [Realtime](https://github.com/supabase/realtime) on Elixir server, mis võimaldab kuulata PostgreSQL-i sisestusi, uuendusi ja kustutusi veebisokkide abil. Realtime küsib Postgres'i sisseehitatud replikatsioonifunktsioone andmebaasi muudatuste kohta, konverteerib muudatused JSON-iks ja edastab seejärel JSON-i üle websocketi volitatud klientidele.
- [PostgREST](http://postgrest.org/) on veebiserver, mis muudab teie PostgreSQL andmebaasi otse RESTful API-ks
- [pg_graphql](http://github.com/supabase/pg_graphql/) on PostgreSQLi laiendus, mis avab GraphQL API
- [Storage](https://github.com/supabase/storage-api) pakub RESTful liidest S3-s salvestatud failide haldamiseks, kasutades Postgres'i õiguste haldamiseks.
- [postgres-meta](https://github.com/supabase/postgres-meta) on RESTful API oma Postgres'i haldamiseks, mis võimaldab tabelite hankimist, rollide lisamist ja päringute käivitamist jne.
- [GoTrue](https://github.com/netlify/gotrue) on SWT-põhine API kasutajate haldamiseks ja SWT-tokenite väljastamiseks.
- [Kong](https://github.com/Kong/kong) on pilvepõhine API-värav.

#### Klientide raamatukogud

Meie lähenemine kliendiraamatukogudele on modulaarne. Iga alamraamatukogu on iseseisev implementatsioon ühe välissüsteemi jaoks. See on üks viis, kuidas me toetame olemasolevaid vahendeid.

<table style="table-layout:fixed; white-space: nowrap;">
  <tr>
    <th>Keel</th>
    <th>Klient</th>
    <th colspan="5">Funktsioon-kliendid (komplekteeritud Supabase'i kliendiga)</th>
  </tr>
  
  <tr>
    <th></th>
    <th>Supabase</th>
    <th><a href="https://github.com/postgrest/postgrest" target="_blank" rel="noopener noreferrer">PostgREST</a></th>
    <th><a href="https://github.com/supabase/gotrue" target="_blank" rel="noopener noreferrer">GoTrue</a></th>
    <th><a href="https://github.com/supabase/realtime" target="_blank" rel="noopener noreferrer">Realtime</a></th>
    <th><a href="https://github.com/supabase/storage-api" target="_blank" rel="noopener noreferrer">Storage</a></th>
    <th>Functions</th>
  </tr>
  <!-- TEMPLATE FOR NEW ROW -->
  <!-- START ROW
  <tr>
    <td>lang</td>
    <td><a href="https://github.com/supabase-community/supabase-lang" target="_blank" rel="noopener noreferrer">supabase-lang</a></td>
    <td><a href="https://github.com/supabase-community/postgrest-lang" target="_blank" rel="noopener noreferrer">postgrest-lang</a></td>
    <td><a href="https://github.com/supabase-community/gotrue-lang" target="_blank" rel="noopener noreferrer">gotrue-lang</a></td>
    <td><a href="https://github.com/supabase-community/realtime-lang" target="_blank" rel="noopener noreferrer">realtime-lang</a></td>
    <td><a href="https://github.com/supabase-community/storage-lang" target="_blank" rel="noopener noreferrer">storage-lang</a></td>
  </tr>
  END ROW -->
  
  <th colspan="7">⚡️ Ametlik ⚡️</th>
  
  <tr>
    <td>JavaScript (TypeScript)</td>
    <td><a href="https://github.com/supabase/supabase-js" target="_blank" rel="noopener noreferrer">supabase-js</a></td>
    <td><a href="https://github.com/supabase/supabase-js/tree/master/packages/core/postgrest-js" target="_blank" rel="noopener noreferrer">postgrest-js</a></td>
    <td><a href="https://github.com/supabase/supabase-js/tree/master/packages/core/auth-js" target="_blank" rel="noopener noreferrer">auth-js</a></td>
    <td><a href="https://github.com/supabase/supabase-js/tree/master/packages/core/realtime-js" target="_blank" rel="noopener noreferrer">realtime-js</a></td>
    <td><a href="https://github.com/supabase/supabase-js/tree/master/packages/core/storage-js" target="_blank" rel="noopener noreferrer">storage-js</a></td>
    <td><a href="https://github.com/supabase/supabase-js/tree/master/packages/core/functions-js" target="_blank" rel="noopener noreferrer">functions-js</a></td>
  </tr>
    <tr>
    <td>Flutter</td>
    <td><a href="https://github.com/supabase/supabase-flutter" target="_blank" rel="noopener noreferrer">supabase-flutter</a></td>
    <td><a href="https://github.com/supabase/postgrest-dart" target="_blank" rel="noopener noreferrer">postgrest-dart</a></td>
    <td><a href="https://github.com/supabase/gotrue-dart" target="_blank" rel="noopener noreferrer">gotrue-dart</a></td>
    <td><a href="https://github.com/supabase/realtime-dart" target="_blank" rel="noopener noreferrer">realtime-dart</a></td>
    <td><a href="https://github.com/supabase/storage-dart" target="_blank" rel="noopener noreferrer">storage-dart</a></td>
    <td><a href="https://github.com/supabase/functions-dart" target="_blank" rel="noopener noreferrer">functions-dart</a></td>
  </tr>
  
  <th colspan="7">💚 Kogukond 💚</th>
  
  <tr>
    <td>C#</td>
    <td><a href="https://github.com/supabase-community/supabase-csharp" target="_blank" rel="noopener noreferrer">supabase-csharp</a></td>
    <td><a href="https://github.com/supabase-community/postgrest-csharp" target="_blank" rel="noopener noreferrer">postgrest-csharp</a></td>
    <td><a href="https://github.com/supabase-community/gotrue-csharp" target="_blank" rel="noopener noreferrer">gotrue-csharp</a></td>
    <td><a href="https://github.com/supabase-community/realtime-csharp" target="_blank" rel="noopener noreferrer">realtime-csharp</a></td>
    <td><a href="https://github.com/supabase-community/storage-csharp" target="_blank" rel="noopener noreferrer">storage-csharp</a></td>
    <td><a href="https://github.com/supabase-community/functions-csharp" target="_blank" rel="noopener noreferrer">functions-csharp</a></td>
  </tr>
  <tr>
    <td>Go</td>
    <td>-</td>
    <td><a href="https://github.com/supabase-community/postgrest-go" target="_blank" rel="noopener noreferrer">postgrest-go</a></td>
    <td><a href="https://github.com/supabase-community/gotrue-go" target="_blank" rel="noopener noreferrer">gotrue-go</a></td>
    <td>-</td>
    <td><a href="https://github.com/supabase-community/storage-go" target="_blank" rel="noopener noreferrer">storage-go</a></td>
    <td><a href="https://github.com/supabase-community/functions-go" target="_blank" rel="noopener noreferrer">functions-go</a></td>
  </tr>
  <tr>
    <td>Java</td>
    <td>-</td>
    <td>-</td>
    <td><a href="https://github.com/supabase-community/gotrue-java" target="_blank" rel="noopener noreferrer">gotrue-java</a></td>
    <td>-</td>
    <td><a href="https://github.com/supabase-community/storage-java" target="_blank" rel="noopener noreferrer">storage-java</a></td>
    <td>-</td>
  </tr>
  <tr>
    <td>Kotlin</td>
    <td><a href="https://github.com/supabase-community/supabase-kt" target="_blank" rel="noopener noreferrer">supabase-kt</a></td>
    <td><a href="https://github.com/supabase-community/supabase-kt/tree/master/Postgrest" target="_blank" rel="noopener noreferrer">postgrest-kt</a></td>
    <td><a href="https://github.com/supabase-community/supabase-kt/tree/master/GoTrue" target="_blank" rel="noopener noreferrer">gotrue-kt</a></td>
    <td><a href="https://github.com/supabase-community/supabase-kt/tree/master/Realtime" target="_blank" rel="noopener noreferrer">realtime-kt</a></td>
    <td><a href="https://github.com/supabase-community/supabase-kt/tree/master/Storage" target="_blank" rel="noopener noreferrer">storage-kt</a></td>
    <td><a href="https://github.com/supabase-community/supabase-kt/tree/master/Functions" target="_blank" rel="noopener noreferrer">functions-kt</a></td>
  </tr>
  <tr>
    <td>Python</td>
    <td><a href="https://github.com/supabase-community/supabase-py" target="_blank" rel="noopener noreferrer">supabase-py</a></td>
    <td><a href="https://github.com/supabase-community/postgrest-py" target="_blank" rel="noopener noreferrer">postgrest-py</a></td>
    <td><a href="https://github.com/supabase-community/gotrue-py" target="_blank" rel="noopener noreferrer">gotrue-py</a></td>
    <td><a href="https://github.com/supabase-community/realtime-py" target="_blank" rel="noopener noreferrer">realtime-py</a></td>
    <td><a href="https://github.com/supabase-community/storage-py" target="_blank" rel="noopener noreferrer">storage-py</a></td>
    <td><a href="https://github.com/supabase-community/functions-py" target="_blank" rel="noopener noreferrer">functions-py</a></td>
  </tr>
  <tr>
    <td>Ruby</td>
    <td><a href="https://github.com/supabase-community/supabase-rb" target="_blank" rel="noopener noreferrer">supabase-rb</a></td>
    <td><a href="https://github.com/supabase-community/postgrest-rb" target="_blank" rel="noopener noreferrer">postgrest-rb</a></td>
    <td>-</td>
    <td>-</td>
    <td>-</td>
    <td>-</td>
  </tr>
  <tr>
    <td>Rust</td>
    <td>-</td>
    <td><a href="https://github.com/supabase-community/postgrest-rs" target="_blank" rel="noopener noreferrer">postgrest-rs</a></td>
    <td>-</td>
    <td>-</td>
    <td>-</td>
    <td>-</td>
  </tr>
  <tr>
    <td>Swift</td>
    <td><a href="https://github.com/supabase-community/supabase-swift" target="_blank" rel="noopener noreferrer">supabase-swift</a></td>
    <td><a href="https://github.com/supabase-community/postgrest-swift" target="_blank" rel="noopener noreferrer">postgrest-swift</a></td>
    <td><a href="https://github.com/supabase-community/gotrue-swift" target="_blank" rel="noopener noreferrer">gotrue-swift</a></td>
    <td><a href="https://github.com/supabase-community/realtime-swift" target="_blank" rel="noopener noreferrer">realtime-swift</a></td>
    <td><a href="https://github.com/supabase-community/storage-swift" target="_blank" rel="noopener noreferrer">storage-swift</a></td>
    <td><a href="https://github.com/supabase-community/functions-swift" target="_blank" rel="noopener noreferrer">functions-swift</a></td>
  </tr>
  <tr>
    <td>Godot Engine (GDScript)</td>
    <td><a href="https://github.com/supabase-community/godot-engine.supabase" target="_blank" rel="noopener noreferrer">supabase-gdscript</a></td>
    <td><a href="https://github.com/supabase-community/postgrest-gdscript" target="_blank" rel="noopener noreferrer">postgrest-gdscript</a></td>
    <td><a href="https://github.com/supabase-community/gotrue-gdscript" target="_blank" rel="noopener noreferrer">gotrue-gdscript</a></td>
    <td><a href="https://github.com/supabase-community/realtime-gdscript" target="_blank" rel="noopener noreferrer">realtime-gdscript</a></td>
    <td><a href="https://github.com/supabase-community/storage-gdscript" target="_blank" rel="noopener noreferrer">storage-gdscript</a></td>
    <td><a href="https://github.com/supabase-community/functions-gdscript" target="_blank" rel="noopener noreferrer">functions-gdscript</a></td>
  </tr>
  
</table>

<!--- Remove this list if you're translating to another language, it's hard to keep updated across multiple files-->
<!--- Keep only the link to the list of translation files-->

## Tõlked

- [araabia | العربية](/i18n/README.ar.md)
- [Albaania / Shqip](/i18n/README.sq.md)
- [Bangla / বাংলা](/i18n/README.bn.md)
- [bulgaaria / Български](/i18n/README.bg.md)
- [katalaani / Català](/i18n/README.ca.md)
- [Taani / Dansk](/i18n/README.da.md)
- [Hollandi keel / Nederlands](/i18n/README.nl.md)
- [inglise keel](https://github.com/supabase/supabase)
- [Soome / Suomalainen](/i18n/README.fi.md)
- [Prantsuse / Français](/i18n/README.fr.md)
- [Saksa / Deutsch](/i18n/README.de.md)
- [Kreeka / Ελληνικά](/i18n/README.gr.md)
- [heebrea / עברית](/i18n/README.he.md)
- [Hindi / हिंदी](/i18n/README.hi.md)
- [Ungari / Magyar](/i18n/README.hu.md)
- [Nepali / नेपाली](/i18n/README.ne.md)
- [Indoneesia / Bahasa Indonesia](/i18n/README.id.md)
- [Itaalia keel / Italiano](/i18n/README.it.md)
- [Jaapani / 日本語](/i18n/README.jp.md)
- [Korea / 한국어](/i18n/README.ko.md)
- [Malai / Bahasa Malaysia](/i18n/README.ms.md)
- [Norra keel (Bokmål) / Norsk (Bokmål)](/i18n/README.nb-no.md)
- [Pärsia keel / فارسی](/i18n/README.fa.md)
- [Poola / Polski](/i18n/README.pl.md)
- [Portugali / Português](/i18n/README.pt.md)
- [Portugali (Brasiilia) / Português Brasileiro](/i18n/README.pt-br.md)
- [Rumeenia / Română](/i18n/README.ro.md)
- [Vene / Pусский](/i18n/README.ru.md)
- [Serbia / Srpski](/i18n/README.sr.md)
- [singhala / සිංහල](/i18n/README.si.md)
- [Hispaania / Español](/i18n/README.es.md)
- [Lihtsustatud hiina keel / 简体中文](/i18n/README.zh-cn.md)
- [Rootsi / Svenska](/i18n/README.sv.md)
- [Tai / ไทย](/i18n/README.th.md)
- [Traditsiooniline hiina keel / 繁體中文](/i18n/README.zh-tw.md)
- [Turkish / Türkçe](/i18n/README.tr.md)
- [Ukraina / Українська](/i18n/README.uk.md)
- [Vietnami keel / Tiếng Việt](/i18n/README.vi-vn.md)
- [Tõlgete loetelu](/i18n/languages.md) <!--- Keep only this -->

---

## Sponsorid

[![Uus sponsor](https://user-images.githubusercontent.com/10214025/90518111-e74bbb00-e198-11ea-8f88-c9e3c1aa4b5b.png)](https://github.com/sponsors/supabase)


## Links discovered
- [Supabase](https://supabase.com)
- [Dokumendid](https://supabase.com/docs/guides/database)
- [Dokumendid](https://supabase.com/docs/guides/auth)
- [Dokumendid](https://supabase.com/docs/guides/api#rest-api-overview)
- [Dokumendid](https://supabase.com/docs/guides/api#graphql-api-overview)
- [Dokumendid](https://supabase.com/docs/guides/api#realtime-api-overview)
- [Dokumendid](https://supabase.com/docs/guides/database/functions)
- [Docs](https://supabase.com/docs/guides/functions)
- [Dokumendid](https://supabase.com/docs/guides/storage)
- [Supabase Dashboard](https://raw.githubusercontent.com/supabase/supabase/master/apps/www/public/images/github/supabase-dashboard.png)
- [supabase.com/docs](https://supabase.com/docs)
- [Getting Started](https://github.com/supabase/supabase/blob/master/DEVELOPERS.md)
- [Ühenduse foorum](https://github.com/supabase/supabase/discussions)
- [GitHub Issues](https://github.com/supabase/supabase/issues)
- [E-posti tugi](https://supabase.com/docs/support#business-support)
- [Discord](https://discord.supabase.com)
- [supabase.com/dashboard](https://supabase.com/dashboard)
- [[status](https://supabase.com/docs/guides/getting-started/features#feature-status)
- [hostitud platvorm](https://supabase.com/dashboard)
- [ise hostida](https://supabase.com/docs/guides/hosting/overview)
- [arendada lokaalselt](https://supabase.com/docs/guides/local-development)
- [Arhitektuur](https://github.com/supabase/supabase/blob/master/apps/docs/public/img/supabase-architecture.svg)
- [PostgreSQL](https://www.postgresql.org/)
- [Realtime](https://github.com/supabase/realtime)
- [PostgREST](http://postgrest.org/)
- [pg_graphql](http://github.com/supabase/pg_graphql/)
- [Storage](https://github.com/supabase/storage-api)
- [postgres-meta](https://github.com/supabase/postgres-meta)
- [GoTrue](https://github.com/netlify/gotrue)
- [Kong](https://github.com/Kong/kong)
- [araabia | العربية](https://github.com/supabase/supabase/blob/master/i18n/README.ar.md)
- [Albaania / Shqip](https://github.com/supabase/supabase/blob/master/i18n/README.sq.md)
- [Bangla / বাংলা](https://github.com/supabase/supabase/blob/master/i18n/README.bn.md)
- [bulgaaria / Български](https://github.com/supabase/supabase/blob/master/i18n/README.bg.md)
- [katalaani / Català](https://github.com/supabase/supabase/blob/master/i18n/README.ca.md)
- [Taani / Dansk](https://github.com/supabase/supabase/blob/master/i18n/README.da.md)
- [Hollandi keel / Nederlands](https://github.com/supabase/supabase/blob/master/i18n/README.nl.md)
- [inglise keel](https://github.com/supabase/supabase)
- [Soome / Suomalainen](https://github.com/supabase/supabase/blob/master/i18n/README.fi.md)
- [Prantsuse / Français](https://github.com/supabase/supabase/blob/master/i18n/README.fr.md)
- [Saksa / Deutsch](https://github.com/supabase/supabase/blob/master/i18n/README.de.md)
- [Kreeka / Ελληνικά](https://github.com/supabase/supabase/blob/master/i18n/README.gr.md)
- [heebrea / עברית](https://github.com/supabase/supabase/blob/master/i18n/README.he.md)
- [Hindi / हिंदी](https://github.com/supabase/supabase/blob/master/i18n/README.hi.md)
- [Ungari / Magyar](https://github.com/supabase/supabase/blob/master/i18n/README.hu.md)
- [Nepali / नेपाली](https://github.com/supabase/supabase/blob/master/i18n/README.ne.md)
- [Indoneesia / Bahasa Indonesia](https://github.com/supabase/supabase/blob/master/i18n/README.id.md)
- [Itaalia keel / Italiano](https://github.com/supabase/supabase/blob/master/i18n/README.it.md)
- [Jaapani / 日本語](https://github.com/supabase/supabase/blob/master/i18n/README.jp.md)
- [Korea / 한국어](https://github.com/supabase/supabase/blob/master/i18n/README.ko.md)
- [Malai / Bahasa Malaysia](https://github.com/supabase/supabase/blob/master/i18n/README.ms.md)
- [Norra keel (Bokmål) / Norsk (Bokmål)](https://github.com/supabase/supabase/blob/master/i18n/README.nb-no.md)
- [Pärsia keel / فارسی](https://github.com/supabase/supabase/blob/master/i18n/README.fa.md)
- [Poola / Polski](https://github.com/supabase/supabase/blob/master/i18n/README.pl.md)
- [Portugali / Português](https://github.com/supabase/supabase/blob/master/i18n/README.pt.md)
- [Portugali (Brasiilia) / Português Brasileiro](https://github.com/supabase/supabase/blob/master/i18n/README.pt-br.md)
- [Rumeenia / Română](https://github.com/supabase/supabase/blob/master/i18n/README.ro.md)
- [Vene / Pусский](https://github.com/supabase/supabase/blob/master/i18n/README.ru.md)
- [Serbia / Srpski](https://github.com/supabase/supabase/blob/master/i18n/README.sr.md)
- [singhala / සිංහල](https://github.com/supabase/supabase/blob/master/i18n/README.si.md)
- [Hispaania / Español](https://github.com/supabase/supabase/blob/master/i18n/README.es.md)
- [Lihtsustatud hiina keel / 简体中文](https://github.com/supabase/supabase/blob/master/i18n/README.zh-cn.md)
- [Rootsi / Svenska](https://github.com/supabase/supabase/blob/master/i18n/README.sv.md)
- [Tai / ไทย](https://github.com/supabase/supabase/blob/master/i18n/README.th.md)
- [Traditsiooniline hiina keel / 繁體中文](https://github.com/supabase/supabase/blob/master/i18n/README.zh-tw.md)
- [Turkish / Türkçe](https://github.com/supabase/supabase/blob/master/i18n/README.tr.md)
- [Ukraina / Українська](https://github.com/supabase/supabase/blob/master/i18n/README.uk.md)
- [Vietnami keel / Tiếng Việt](https://github.com/supabase/supabase/blob/master/i18n/README.vi-vn.md)
- [Tõlgete loetelu](https://github.com/supabase/supabase/blob/master/i18n/languages.md)
- [![Uus sponsor](https://user-images.githubusercontent.com/10214025/90518111-e74bbb00-e198-11ea-8f88-c9e3c1aa4b5b.png)
- [PostgREST](https://github.com/postgrest/postgrest)
- [GoTrue](https://github.com/supabase/gotrue)
- [supabase-lang](https://github.com/supabase-community/supabase-lang)
- [postgrest-lang](https://github.com/supabase-community/postgrest-lang)
- [gotrue-lang](https://github.com/supabase-community/gotrue-lang)
- [realtime-lang](https://github.com/supabase-community/realtime-lang)
- [storage-lang](https://github.com/supabase-community/storage-lang)
- [supabase-js](https://github.com/supabase/supabase-js)
- [postgrest-js](https://github.com/supabase/supabase-js/tree/master/packages/core/postgrest-js)
- [auth-js](https://github.com/supabase/supabase-js/tree/master/packages/core/auth-js)
- [realtime-js](https://github.com/supabase/supabase-js/tree/master/packages/core/realtime-js)
- [storage-js](https://github.com/supabase/supabase-js/tree/master/packages/core/storage-js)
- [functions-js](https://github.com/supabase/supabase-js/tree/master/packages/core/functions-js)
- [supabase-flutter](https://github.com/supabase/supabase-flutter)
- [postgrest-dart](https://github.com/supabase/postgrest-dart)
- [gotrue-dart](https://github.com/supabase/gotrue-dart)
- [realtime-dart](https://github.com/supabase/realtime-dart)
- [storage-dart](https://github.com/supabase/storage-dart)
- [functions-dart](https://github.com/supabase/functions-dart)
- [supabase-csharp](https://github.com/supabase-community/supabase-csharp)
- [postgrest-csharp](https://github.com/supabase-community/postgrest-csharp)
- [gotrue-csharp](https://github.com/supabase-community/gotrue-csharp)
- [realtime-csharp](https://github.com/supabase-community/realtime-csharp)
- [storage-csharp](https://github.com/supabase-community/storage-csharp)
- [functions-csharp](https://github.com/supabase-community/functions-csharp)
- [postgrest-go](https://github.com/supabase-community/postgrest-go)
- [gotrue-go](https://github.com/supabase-community/gotrue-go)
- [storage-go](https://github.com/supabase-community/storage-go)
- [functions-go](https://github.com/supabase-community/functions-go)
- [gotrue-java](https://github.com/supabase-community/gotrue-java)

--- packages/ai-commands/README.md ---
# ai-commands

## Main purpose

This package contains all features involving AI and LLMs (eg. via OpenAI API).
Each feature is implemented as a function which can be easily tested for regressions.

The streaming functions only work on Edge runtime so they can only be imported via a special `edge` subpath like so:

```ts
import { chatRlsPolicy } from 'ai-commands/edge'
```


--- packages/icons/README.md ---
# ./packages/icons

This package contains custom Supabase icons that can be used alongside other icon libraries.

## Documentation

**For complete documentation, usage examples, and guidelines, see the [Design System](../../apps/design-system/content/docs/icons.mdx)**

## Quick start

```jsx
import { BucketAdd, Database, Auth } from 'icons'

function MyComponent() {
  return (
    <>
      <BucketAdd size={24} className="text-foreground-muted" />
      <Database size={16} strokeWidth={1} />
      <Auth size={20} />
    </>
  )
}
```

### Adding new custom icons

1. Add your SVG file to `src/raw-icons/` (kebab-case name)
2. Run `npm run build:icons` in this directory
3. Import and use your new icon

For detailed instructions, examples, and troubleshooting, see the [Design System](../../apps/design-system/content/docs/icons.mdx).


## Links discovered
- [Design System](https://github.com/supabase/supabase/blob/master/apps/design-system/content/docs/icons.mdx)

--- packages/tsconfig/README.md ---
# `tsconfig`

These are base shared `tsconfig.json`s from which all other `tsconfig.json`'s inherit from.
