diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml
index 194050ee191..e0c4af21e53 100644
--- a/docker/docker-compose.yml
+++ b/docker/docker-compose.yml
@@ -41,7 +41,7 @@ services:
rest:
container_name: supabase-rest
- image: postgrest/postgrest:latest
+ image: postgrest/postgrest:nightly-2021-03-05-19-03-d3a8b5f
ports:
- ${REST_PORT}:3000
depends_on:
@@ -49,9 +49,10 @@ services:
restart: always
environment:
PGRST_DB_URI: postgres://postgres:${POSTGRES_PASSWORD}@db:${POSTGRES_PORT}/postgres
- PGRST_DB_SCHEMA: public
+ PGRST_DB_SCHEMA: public, storage
PGRST_DB_ANON_ROLE: anon
PGRST_JWT_SECRET: ${JWT_SECRET}
+
realtime:
container_name: supabase-realtime
image: supabase/realtime:latest
@@ -71,6 +72,30 @@ services:
# Disable JWT Auth locally. The JWT_SECRET will be ignored.
SECURE_CHANNELS: 'false'
JWT_SECRET: ${JWT_SECRET}
+
+ storage:
+ image: supabase/storage-api:v0.8.3
+ ports:
+ - '5000:5000'
+ depends_on:
+ - db
+ - rest
+ restart: on-failure
+ environment:
+ ANON_KEY: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJzdXBhYmFzZSIsImlhdCI6MTYwMzk2ODgzNCwiZXhwIjoxNjkwNDQzMTM1LCJhdWQiOiIiLCJzdWIiOiIiLCJSb2xlIjoiYW5vbiIsImp0aSI6ImQ1N2ZkMjFlLTNkYjQtNDk3YS1hNGViLTI4ZDY3Yzg2NjEwMiJ9.YySz_BoYxYco0b2aP3iBMt-dVUqC6RpnQiSb4Zt-L-o
+ SERVICE_KEY: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJzdXBhYmFzZSIsImlhdCI6MTYwMzk2ODgzNCwiZXhwIjoxNjkwNDQzMTM1LCJhdWQiOiIiLCJyb2xlIjoic2VydmljZV9yb2xlIiwianRpIjoiOTk4MzJiYmItNDdhNS00NzJhLTgxMDQtYzY4ZGVjZDQyZDVlIn0._DToQdxDcSLsp3tPpacsxswv0p1KRti0QOTmnbfs5JY
+ PROJECT_REF: bjwdssmqcnupljrqypxz # can be any random string
+ POSTGREST_URL: http://rest:3000
+ PGRST_JWT_SECRET: ${JWT_SECRET}
+ DATABASE_URL: postgres://postgres:${POSTGRES_PASSWORD}@db:${POSTGRES_PORT}/postgres
+ PGOPTIONS: "-c search_path=storage"
+ FILE_SIZE_LIMIT: 52428800
+ REGION: us-east-1 # region where your bucket is located
+ GLOBAL_S3_BUCKET: supa-storage-testing # name of s3 bucket where you want to store objects
+ # AWS_ACCESS_KEY_ID: replace-with-your-aws-key
+ # AWS_SECRET_ACCESS_KEY: replace-with-your-aws-secret
+ STORAGE_BACKEND: file
+ FILE_STORAGE_BACKEND_PATH: /var/data/storage
db:
container_name: supabase-db
build:
@@ -85,4 +110,4 @@ services:
POSTGRES_DB: postgres
POSTGRES_USER: postgres
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
- POSTGRES_PORT: ${POSTGRES_PORT}
+ POSTGRES_PORT: ${POSTGRES_PORT}
\ No newline at end of file
diff --git a/docker/dockerfiles/kong/kong.yml b/docker/dockerfiles/kong/kong.yml
index 1adfb5c8c58..659d804783e 100644
--- a/docker/dockerfiles/kong/kong.yml
+++ b/docker/dockerfiles/kong/kong.yml
@@ -39,6 +39,16 @@ services:
- name: key-auth
config:
hide_credentials: true
+- name: storage-v1
+ _comment: 'Storage: /storage/v1/* -> http://storage-api:5000/*'
+ url: http://storage:5000/
+ routes:
+ - name: storage-v1-all
+ strip_path: true
+ paths:
+ - /storage/v1/
+ plugins:
+ - name: cors
consumers:
- username: 'anon-key'
keyauth_credentials:
diff --git a/docker/dockerfiles/postgres/Dockerfile b/docker/dockerfiles/postgres/Dockerfile
index cbec306808c..cd03b29b241 100644
--- a/docker/dockerfiles/postgres/Dockerfile
+++ b/docker/dockerfiles/postgres/Dockerfile
@@ -2,6 +2,7 @@ FROM supabase/postgres:0.13.0
COPY 00-initial-schema.sql /docker-entrypoint-initdb.d/00-initial-schema.sql
COPY auth-schema.sql /docker-entrypoint-initdb.d/auth-schema.sql
+COPY storage-schema.sql /docker-entrypoint-initdb.d/storage-schema.sql
# Build time defaults
ARG build_POSTGRES_DB=postgres
diff --git a/docker/dockerfiles/postgres/storage-schema.sql b/docker/dockerfiles/postgres/storage-schema.sql
new file mode 100644
index 00000000000..c879c6b6234
--- /dev/null
+++ b/docker/dockerfiles/postgres/storage-schema.sql
@@ -0,0 +1,111 @@
+CREATE SCHEMA IF NOT EXISTS storage AUTHORIZATION postgres;
+
+grant usage on schema storage to postgres, anon, authenticated, service_role;
+alter default privileges in schema storage grant all on tables to postgres, anon, authenticated, service_role;
+alter default privileges in schema storage grant all on functions to postgres, anon, authenticated, service_role;
+alter default privileges in schema storage grant all on sequences to postgres, anon, authenticated, service_role;
+
+DROP TABLE IF EXISTS "storage"."buckets";
+CREATE TABLE "storage"."buckets" (
+ "id" text not NULL,
+ "name" text NOT NULL,
+ "owner" uuid,
+ "created_at" timestamptz DEFAULT now(),
+ "updated_at" timestamptz DEFAULT now(),
+ CONSTRAINT "buckets_owner_fkey" FOREIGN KEY ("owner") REFERENCES "auth"."users"("id"),
+ PRIMARY KEY ("id")
+);
+CREATE UNIQUE INDEX "bname" ON "storage"."buckets" USING BTREE ("name");
+
+DROP TABLE IF EXISTS "storage"."objects";
+CREATE TABLE "storage"."objects" (
+ "id" uuid NOT NULL DEFAULT extensions.uuid_generate_v4(),
+ "bucket_id" text,
+ "name" text,
+ "owner" uuid,
+ "created_at" timestamptz DEFAULT now(),
+ "updated_at" timestamptz DEFAULT now(),
+ "last_accessed_at" timestamptz DEFAULT now(),
+ "metadata" jsonb,
+ CONSTRAINT "objects_bucketId_fkey" FOREIGN KEY ("bucket_id") REFERENCES "storage"."buckets"("id"),
+ CONSTRAINT "objects_owner_fkey" FOREIGN KEY ("owner") REFERENCES "auth"."users"("id"),
+ PRIMARY KEY ("id")
+);
+CREATE UNIQUE INDEX "bucketid_objname" ON "storage"."objects" USING BTREE ("bucket_id","name");
+CREATE INDEX name_prefix_search ON storage.objects(name text_pattern_ops);
+
+ALTER TABLE storage.objects ENABLE ROW LEVEL SECURITY;
+
+CREATE OR REPLACE FUNCTION storage.foldername(name text)
+ RETURNS text[]
+ LANGUAGE plpgsql
+AS $function$
+DECLARE
+_parts text[];
+BEGIN
+ select string_to_array(name, '/') into _parts;
+ return _parts[1:array_length(_parts,1)-1];
+END
+$function$;
+
+CREATE OR REPLACE FUNCTION storage.filename(name text)
+ RETURNS text
+ LANGUAGE plpgsql
+AS $function$
+DECLARE
+_parts text[];
+BEGIN
+ select string_to_array(name, '/') into _parts;
+ return _parts[array_length(_parts,1)];
+END
+$function$;
+
+CREATE OR REPLACE FUNCTION storage.extension(name text)
+ RETURNS text
+ LANGUAGE plpgsql
+AS $function$
+DECLARE
+_parts text[];
+_filename text;
+BEGIN
+ select string_to_array(name, '/') into _parts;
+ select _parts[array_length(_parts,1)] into _filename;
+ return split_part(_filename, '.', 2);
+END
+$function$;
+
+CREATE OR REPLACE FUNCTION storage.search(prefix text, bucketname text, limits int DEFAULT 100, levels int DEFAULT 1, offsets int DEFAULT 0)
+ RETURNS TABLE (
+ name text,
+ id uuid,
+ updated_at TIMESTAMPTZ,
+ created_at TIMESTAMPTZ,
+ last_accessed_at TIMESTAMPTZ,
+ metadata jsonb
+ )
+ LANGUAGE plpgsql
+AS $function$
+DECLARE
+_bucketId text;
+BEGIN
+ select buckets."id" from buckets where buckets.name=bucketname limit 1 into _bucketId;
+ return query
+ with files_folders as (
+ select ((string_to_array(objects.name, '/'))[levels]) as folder
+ from objects
+ where objects.name ilike prefix || '%'
+ and bucket_id = _bucketId
+ GROUP by folder
+ limit limits
+ offset offsets
+ )
+ select files_folders.folder as name, objects.id, objects.updated_at, objects.created_at, objects.last_accessed_at, objects.metadata from files_folders
+ left join objects
+ on prefix || files_folders.folder = objects.name
+ where objects.id is null or objects.bucket_id=_bucketId;
+END
+$function$;
+
+GRANT ALL PRIVILEGES ON SCHEMA storage TO postgres;
+GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA storage TO postgres;
+GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA storage TO postgres;
\ No newline at end of file
diff --git a/web/docs/guides/auth.mdx b/web/docs/guides/auth.mdx
index 5e488a89fe4..8fd47316bd9 100644
--- a/web/docs/guides/auth.mdx
+++ b/web/docs/guides/auth.mdx
@@ -248,6 +248,31 @@ create policy "Only Blizzard staff can update leaderboard"
);
```
+### Time to live for rows
+
+Policies can also be used to implement TTL or time to live feature that you see in Instagram stories or Snapchat.
+In the following example, rows of `stories` table are available only if they have been created within the last 24 hours.
+
+```sql
+-- 1. Create table
+create table if not exists stories (
+ id uuid not null primary key DEFAULT uuid_generate_v4(),
+ created_at timestamp with time zone default timezone('utc' :: text, now()) not null,
+ content text not null
+);
+
+-- 2. Enable RLS
+alter table stories
+ enable row level security;
+
+-- 3. Create Policy
+create policy "Stories are live for a day"
+ on stories
+ for select using (
+ created_at > (current_timestamp - interval '1 day')
+ );
+```
+
### Advanced policies
Use the full power of SQL to build extremely advanced rules.
diff --git a/web/docs/guides/client-libraries.mdx b/web/docs/guides/client-libraries.mdx
index 3daeea8ba50..8bbbf6e4488 100755
--- a/web/docs/guides/client-libraries.mdx
+++ b/web/docs/guides/client-libraries.mdx
@@ -116,7 +116,7 @@ import 'package:supabase/supabase.dart';
void main() async {
final client = SupabaseClient('supabaseUrl', 'supabaseKey');
- // Sign up user with email and password
+ // Sign in user with email and password
final response = await client
.auth
.signIn(email: 'example@email.com', password: 'example-password');
@@ -170,7 +170,7 @@ import 'package:supabase/supabase.dart';
void main() async {
final client = SupabaseClient('supabaseUrl', 'supabaseKey');
- // Sign up user with email and password
+ // Sign in user with email and magic link
final response = await client
.auth
.signIn(email: 'example@email.com');
@@ -270,7 +270,7 @@ import 'package:supabase/supabase.dart';
void main() async {
final client = SupabaseClient('supabaseUrl', 'supabaseKey');
- // Sign up user with email and password
+ // Query tables and fetch related data in one round-trip, using select()
final response = await client
.from('countries')
.select('name')
@@ -320,7 +320,7 @@ import 'package:supabase/supabase.dart';
void main() async {
final client = SupabaseClient('supabaseUrl', 'supabaseKey');
- // Sign up user with email and password
+ // When fetching data, use advanced filtering to only extract data, that you need.
final response = await client
.from('cities')
.select('name,country_id')
@@ -374,7 +374,7 @@ import 'package:supabase/supabase.dart';
void main() async {
final client = SupabaseClient('supabaseUrl', 'supabaseKey');
- // Sign up user with email and password
+ // Create data easily, using insert()
final response = await client
.from('cities')
.insert([
@@ -430,7 +430,7 @@ import 'package:supabase/supabase.dart';
void main() async {
final client = SupabaseClient('supabaseUrl', 'supabaseKey');
- // Sign up user with email and password
+ // Listen to realtime database changes, using subscribe()
final response = await client
.from('countries')
.on(SupabaseEventTypes.all, (payload) {
@@ -483,7 +483,7 @@ import 'package:supabase/supabase.dart';
void main() async {
final client = SupabaseClient('supabaseUrl', 'supabaseKey');
- // Sign up user with email and password
+ // You can even listen to Row Level changes
final response = await client
.from('countries:id.eq.200')
.on(SupabaseEventTypes.update, (payload) {
diff --git a/web/docs/guides/examples.mdx b/web/docs/guides/examples.mdx
index 90a84f66f7d..efde677ad7b 100644
--- a/web/docs/guides/examples.mdx
+++ b/web/docs/guides/examples.mdx
@@ -56,6 +56,7 @@ Build a basic Todo List with Supabase and your favorite frontend framework:
- Flutter Supabase Authentication [Blog](https://www.sandromaglione.com/2021/04/24/flutter-supabase-authentication/)
- Supabase in 6 minutes [Video](https://www.youtube.com/watch?v=c8DNV9yl0mg)
- Let's build SupaAuth - Build an Authentication System using Supabase, Next.js and Typescript [6-part Blog Series](https://aalam.in/blog/supabase-auth-intro-setup-next)
+- In-depth self-hosting guide using Nginx [Blog](https://dev.to/chronsyn/self-hosting-with-supabase-1aii)
### Example apps
diff --git a/web/docs/guides/with-flutter.mdx b/web/docs/guides/with-flutter.mdx
new file mode 100644
index 00000000000..b1bcf43468d
--- /dev/null
+++ b/web/docs/guides/with-flutter.mdx
@@ -0,0 +1,611 @@
+---
+id: with-flutter
+title: "Quickstart: Flutter"
+description: Learn how to use Supabase in your Flutter App.
+---
+
+import Tabs from '@theme/Tabs';
+import TabItem from '@theme/TabItem';
+
+## Intro
+
+This example provides the steps to build a simple user management app (from scratch!) using Supabase and Flutter. It includes:
+
+- Supabase [Database](/docs/guides/database): a Postgres database for storing your user data.
+- Supabase [Auth](/docs/guides/auth): users can sign in with magic links (no passwords, only email).
+
+- [Row Level Security](/docs/guides/auth#row-level-security): data is protected so that individuals can only access their own data.
+- Instant [APIs](/docs/guides/api): APIs will be automatically generated when you create your database tables.
+
+By the end of this guide you'll have an app which allows users to login and update some basic profile details:
+
+
+
+### Github
+Whenever you get stuck at any point, take a look at [this repo](https://github.com/dshukertjr/flutter_supabase_quickstart).
+
+## Project set up
+
+Before we start building we're going to set up our Database and API. This is as simple as starting a new Project in Supabase
+and then creating a "schema" inside the database.
+
+### Create a project
+
+1. Go to [app.supabase.io](https://app.supabase.io).
+1. Click on "New Project".
+1. Enter your project details.
+1. Wait for the new database to launch.
+
+
+### Set up the database schema
+
+Now we are going to set up the database schema. We can use the "User Management Starter" quickstart in the SQL Editor,
+or you can just copy/paste the SQL from below and run it yourself.
+
+
+
+
+```sh
+1. Go to the "SQL" section.
+2. Click "User Management Starter".
+3. Click "Run".
+```
+
+
+
+
+
+
+```sql
+-- Create a table for public "profiles"
+create table profiles (
+ id uuid references auth.users not null,
+ updated_at timestamp with time zone,
+ username text unique,
+ avatar_url text,
+ website text,
+
+ primary key (id),
+ unique(username),
+ constraint username_length check (char_length(username) >= 3)
+);
+
+alter table profiles enable row level security;
+
+create policy "Public profiles are viewable by everyone."
+ on profiles for select
+ using ( true );
+
+create policy "Users can insert their own profile."
+ on profiles for insert
+ with check ( auth.uid() = id );
+
+create policy "Users can update own profile."
+ on profiles for update
+ using ( auth.uid() = id );
+
+-- Set up Realtime!
+begin;
+ drop publication if exists supabase_realtime;
+ create publication supabase_realtime;
+commit;
+alter publication supabase_realtime add table profiles;
+
+-- Set up Storage!
+insert into storage.buckets (id, name)
+values ('avatars', 'avatars');
+
+create policy "Avatar images are publicly accessible."
+ on storage.objects for select
+ using ( bucket_id = 'avatars' );
+
+create policy "Anyone can upload an avatar."
+ on storage.objects for insert
+ with check ( bucket_id = 'avatars' );
+
+```
+
+
+
+
+
+### Get the API Keys
+
+
+Now that you've created some database tables, you are ready to insert data using the auto-generated API.
+We just need to get the URL and `anon` key from the API settings.
+
+
+
+
+```sh
+1. Go to the "Settings" section.
+2. Click "API" in the sidebar.
+3. Find your API URL in this page.
+4. Find your "anon" key on this page.
+```
+
+
+
+
+
+
+## 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://github.com/supabase/supabase-flutter)
+
+Open the app and copy and paste the following inside `pubspec.yaml` under `dependencies`.
+
+```yaml
+supabase_flutter: ^0.0.4
+```
+
+Run `flutter pub get` to install the dependencies.
+
+### Setup deep links
+
+Now that we have the dependencies installed let's setup deep links so users who have logged in via magic link or OAuth can come back to the app.
+
+```sh
+1. Go to the "Authentication" section.
+2. Click "Settings" in the sidebar.
+3. Type `io.supabase.flutterquickstart://login-callback/` in the Additional Redirect URLs input field.
+4. Hit save.
+```
+
+
+
+That is it on Supabase's end and the rest are platform specific settings:
+
+For Android, add an intent-filter to enable deep linking:
+```xml title="android/app/src/main/AndroidManifest.xml"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+For iOS add CFBundleURLTypes to enable deep linking:
+```xml title="ios/Runner/Info.plist"
+
+
+
+
+
+
+ CFBundleURLTypes
+
+
+ CFBundleTypeRole
+ Editor
+ CFBundleURLSchemes
+
+ io.supabase.flutterquickstart
+
+
+
+
+
+
+```
+
+For web:
+
+There are no additional configurations.
+
+### 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.
+
+```dart title="lib/main.dart"
+void main() {
+ WidgetsFlutterBinding.ensureInitialized();
+
+ Supabase.initialize(
+ url: '[YOUR_SUPABASE_URL]',
+ anonKey: '[YOUR_SUPABASE_ANNON_KEY]',
+ authCallbackUrlHostname: 'login-callback',
+ );
+ runApp(MyApp());
+}
+```
+
+### Set up AuthState
+
+In order to handle deep links for Android and iOS, let's create a class that will do just that.
+`supabase_flutter` plugin comes with `SupabaseAuthState` class where we can inherit from to react to various deep link events.
+
+```dart title="lib/components/auth_state.dart"
+import 'package:flutter/material.dart';
+import 'package:supabase/supabase.dart';
+import 'package:supabase_flutter/supabase_flutter.dart';
+
+class AuthState extends SupabaseAuthState {
+ @override
+ void onUnauthenticated() {
+ Navigator.of(context).pushNamedAndRemoveUntil('/login', (route) => false);
+ }
+
+ @override
+ void onAuthenticated(Session session) {
+ Navigator.of(context).pushNamedAndRemoveUntil('/account', (route) => false);
+ }
+
+ @override
+ void onPasswordRecovery(Session session) {}
+
+ @override
+ void onErrorAuthenticating(String message) {
+ print('Error authenticating $message');
+ }
+}
+```
+
+Let's also create a constant file to make it easier to use Supabase client.
+
+```dart title="lib/utils/constants.dart"
+import 'package:supabase_flutter/supabase_flutter.dart';
+
+final supabase = Supabase.instance.client;
+```
+
+### Set up Splash Screen
+
+Let's create a splash screen that will be shown to users right after they open the app.
+This splash screen inherits `AuthState` to redirect users to the appropriate pages depending on their authentication state.
+
+```dart title="lib/pages/splash_page.dart"
+import 'package:flutter/material.dart';
+import 'package:supabase_flutter_guide/components/auth_state.dart';
+
+class SplashPage extends StatefulWidget {
+ const SplashPage({Key? key}) : super(key: key);
+
+ @override
+ _SplashPageState createState() => _SplashPageState();
+}
+
+class _SplashPageState extends AuthState {
+ @override
+ void initState() {
+ recoverSupabaseSession();
+ super.initState();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return const Scaffold(
+ body: Center(child: CircularProgressIndicator()),
+ );
+ }
+}
+```
+
+### Set up a Login page
+
+Let's create a Flutter widget to manage logins and sign ups.
+We'll use Magic Links, so users can sign in with their email without using passwords.
+This page will also inherit `AuthState` as it will handle user login.
+
+```dart title="lib/pages/login_page.dart"
+import 'package:flutter/foundation.dart';
+import 'package:flutter/material.dart';
+import 'package:supabase_flutter_guide/components/auth_state.dart';
+import 'package:supabase/supabase.dart';
+import 'package:supabase_flutter_guide/utils/constants.dart';
+
+class LoginPage extends StatefulWidget {
+ const LoginPage({Key? key}) : super(key: key);
+
+ @override
+ _LoginPageState createState() => _LoginPageState();
+}
+
+class _LoginPageState extends AuthState {
+ bool _isLoading = false;
+ late final TextEditingController _emailController;
+
+ Future _signIn() async {
+ setState(() {
+ _isLoading = true;
+ });
+ final response = await supabase.auth.signIn(
+ email: _emailController.text,
+ options: AuthOptions(
+ redirectTo: kIsWeb
+ ? null
+ : 'io.supabase.flutterquickstart://login-callback/'));
+ if (response.error != null) {
+ ScaffoldMessenger.of(context).showSnackBar(SnackBar(
+ content: Text(response.error!.message),
+ backgroundColor: Colors.red,
+ ));
+ } else {
+ ScaffoldMessenger.of(context).showSnackBar(
+ const SnackBar(content: Text('Check your email for login link!')));
+ }
+ setState(() {
+ _emailController.clear();
+ _isLoading = false;
+ });
+ }
+
+ @override
+ void initState() {
+ _emailController = TextEditingController();
+ super.initState();
+ }
+
+ @override
+ void dispose() {
+ _emailController.dispose();
+ 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 ? 'Loading' : 'Send Magic Link'),
+ ),
+ ],
+ ),
+ );
+ }
+}
+```
+
+### 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 component for that called `account_page.dart`.
+Note that this page will also inherit `AuthState` as it will handle user sign out.
+
+```dart title="lib/pages/account_page.dart"
+import 'package:flutter/material.dart';
+import 'package:supabase_flutter_guide/components/auth_state.dart';
+import 'package:supabase_flutter_guide/utils/constants.dart';
+
+class AccountPage extends StatefulWidget {
+ const AccountPage({Key? key}) : super(key: key);
+
+ @override
+ _AccountPageState createState() => _AccountPageState();
+}
+
+class _AccountPageState extends AuthState {
+ late final _usernameController = TextEditingController();
+ late final _websiteController = TextEditingController();
+ var _loading = false;
+
+ Future _getProfile() async {
+ setState(() {
+ _loading = true;
+ });
+ final user = supabase.auth.user()!;
+ final response = await supabase
+ .from('profiles')
+ .select()
+ .eq('id', user.id)
+ .single()
+ .execute();
+ if (response.error != null && response.status != 406) {
+ ScaffoldMessenger.of(context)
+ .showSnackBar(SnackBar(content: Text(response.error!.message)));
+ }
+ if (response.data != null) {
+ _usernameController.text = response.data!['username'] as String;
+ _websiteController.text = response.data!['website'] as String;
+ }
+ setState(() {
+ _loading = false;
+ });
+ }
+
+ Future _updateProfile() async {
+ setState(() {
+ _loading = true;
+ });
+ final userName = _usernameController.text;
+ final website = _websiteController.text;
+ final user = supabase.auth.currentUser;
+ final updates = {
+ 'id': user!.id,
+ 'username': userName,
+ 'website': website,
+ 'updated_at': DateTime.now().toIso8601String(),
+ };
+ final response = await supabase.from('profiles').upsert(updates).execute();
+ if (response.error != null) {
+ ScaffoldMessenger.of(context).showSnackBar(SnackBar(
+ content: Text(response.error!.message),
+ backgroundColor: Colors.red,
+ ));
+ } else {
+ ScaffoldMessenger.of(context).showSnackBar(
+ const SnackBar(content: Text('Successfully updated profile!')));
+ }
+ setState(() {
+ _loading = false;
+ });
+ }
+
+ Future _signOut() async {
+ final response = await supabase.auth.signOut();
+ if (response.error != null) {
+ ScaffoldMessenger.of(context).showSnackBar(SnackBar(
+ content: Text(response.error!.message),
+ backgroundColor: Colors.red,
+ ));
+ }
+ Navigator.of(context).pushReplacementNamed('/login');
+ }
+
+ @override
+ void initState() {
+ _getProfile();
+ super.initState();
+ }
+
+ @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: _updateProfile,
+ child: Text(_loading ? 'Saving...' : 'Update')),
+ const SizedBox(height: 18),
+ ElevatedButton(onPressed: _signOut, child: const Text('Sign Out')),
+ ],
+ ),
+ );
+ }
+}
+```
+
+
+
+### Launch!
+
+Now that we have all the components in place, let's update `lib/main.dart`:
+
+```dart title="lib/main.dart"
+import 'package:flutter/material.dart';
+import 'package:supabase_flutter/supabase_flutter.dart';
+import 'package:supabase_flutter_guide/pages/account_page.dart';
+import 'package:supabase_flutter_guide/pages/login_page.dart';
+import 'package:supabase_flutter_guide/pages/splash_page.dart';
+
+void main() {
+ WidgetsFlutterBinding.ensureInitialized();
+
+ Supabase.initialize(
+ url: '[YOUR_SUPABASE_URL]',
+ anonKey: '[YOUR_SUPABASE_ANNON_KEY]',
+ );
+ runApp(MyApp());
+}
+
+class MyApp extends StatelessWidget {
+ @override
+ Widget build(BuildContext context) {
+ return MaterialApp(
+ title: 'Supabase Flutter',
+ theme: ThemeData.dark().copyWith(
+ primaryColor: Colors.green,
+ accentColor: Colors.green,
+ elevatedButtonTheme: ElevatedButtonThemeData(
+ style: ElevatedButton.styleFrom(
+ onPrimary: Colors.white,
+ primary: Colors.green,
+ ),
+ ),
+ ),
+ initialRoute: '/',
+ routes: {
+ '/': (_) => const SplashPage(),
+ '/login': (_) => const LoginPage(),
+ '/account': (_) => const AccountPage(),
+ },
+ );
+ }
+}
+```
+
+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.
+
+
+
+
+## Next steps
+
+At this stage you have a fully functional application!
+
+- Got a question? [Ask here](https://github.com/supabase/supabase/discussions).
+- Sign in: [app.supabase.io](https://app.supabase.io)
diff --git a/web/sidebars.js b/web/sidebars.js
index 3e4c4533af6..7b387d05ece 100755
--- a/web/sidebars.js
+++ b/web/sidebars.js
@@ -89,6 +89,7 @@ module.exports = {
'guides/with-svelte',
'guides/with-vue-3',
'guides/with-angular',
+ 'guides/with-flutter',
'guides/self-hosting',
],
},
diff --git a/web/spec/supabase.yml b/web/spec/supabase.yml
index b6e3a05e9cc..45f4e397478 100644
--- a/web/spec/supabase.yml
+++ b/web/spec/supabase.yml
@@ -986,6 +986,7 @@ pages:
- Policy permissions required:
- `buckets` permissions: none
- `objects` permissions: `insert`
+ - For React Native, using either `Blob`, `File` or `FormData` does not work as intended. Upload file using `ArrayBuffer` from base64 file data instead, see example below.
examples:
- name: Upload file
isSpotlight: true
@@ -1000,6 +1001,18 @@ pages:
upsert: false
})
```
+ - name: Upload file using `ArrayBuffer` from base64 file data
+ js: |
+ ```js
+ import {decode} from 'base64-arraybuffer'
+
+ const { data, error } = await supabase
+ .storage
+ .from('avatars')
+ .upload('public/avatar1.png', decode('base64FileData'), {
+ contentType: 'image/png'
+ })
+ ```
storage.from.update():
title: 'from.update()'
@@ -1008,6 +1021,7 @@ pages:
- Policy permissions required:
- `buckets` permissions: none
- `objects` permissions: `update` and `select`
+ - For React Native, using either `Blob`, `File` or `FormData` does not work as intended. Update file using `ArrayBuffer` from base64 file data instead, see example below.
examples:
- name: Update file
isSpotlight: true
@@ -1022,6 +1036,18 @@ pages:
upsert: false
})
```
+ - name: Update file using `ArrayBuffer` from base64 file data
+ js: |
+ ```js
+ import {decode} from 'base64-arraybuffer'
+
+ const { data, error } = await supabase
+ .storage
+ .from('avatars')
+ .update('public/avatar1.png', decode('base64FileData'), {
+ contentType: 'image/png'
+ })
+ ```
storage.from.move():
title: 'from.move()'
diff --git a/web/static/img/deeplink-setting.png b/web/static/img/deeplink-setting.png
new file mode 100644
index 00000000000..ebfa4cce24a
Binary files /dev/null and b/web/static/img/deeplink-setting.png differ
diff --git a/web/static/img/supabase-flutter-demo.png b/web/static/img/supabase-flutter-demo.png
new file mode 100644
index 00000000000..11653b261b8
Binary files /dev/null and b/web/static/img/supabase-flutter-demo.png differ
diff --git a/www/_blog/2021-07-22-supabase-launch-week-sql.mdx b/www/_blog/2021-07-22-supabase-launch-week-sql.mdx
index c0c9c2f1d68..4bc4c6fbdf2 100644
--- a/www/_blog/2021-07-22-supabase-launch-week-sql.mdx
+++ b/www/_blog/2021-07-22-supabase-launch-week-sql.mdx
@@ -35,7 +35,7 @@ One of the best things about building Supabase is the community. We're even luck
### Where?
-Blog post coming soon. Follow our [Twitter](twitter.com/supabase) to get notified immediately.
+Blog post coming soon. Follow our [Twitter](https://twitter.com/supabase) to get notified immediately.
## Tuesday 27 July: Auth v2
@@ -51,7 +51,7 @@ What's cooler than Arnie in the 90's? Passwordless logins. We've [already](https
### Where?
-Blog post coming soon. Follow our [Twitter](twitter.com/supabase) to get notified immediately.
+Blog post coming soon. Follow our [Twitter](https://twitter.com/supabase) to get notified immediately.
## Wednesday 28 July: Storage v2
@@ -67,7 +67,7 @@ We shipped [Version 1 of Supabase Storage](https://supabase.io/blog/2021/03/30/s
### Where?
-Blog post coming soon. Follow our [Twitter](twitter.com/supabase) to get notified immediately.
+Blog post coming soon. Follow our [Twitter](https://twitter.com/supabase) to get notified immediately.
## Thursday 29 July: Dashboard v2
@@ -83,7 +83,7 @@ Who needs a futuristic [Hoverboard from 2015](https://www.youtube.com/watch?v=NR
### Where?
-Blog post coming soon. Follow our [Twitter](twitter.com/supabase) to get notified immediately.
+Blog post coming soon. Follow our [Twitter](https://twitter.com/supabase) to get notified immediately.
## Friday 30 July: One more thing
@@ -99,4 +99,4 @@ We all know that the best [SeQueLs](https://www.reddit.com/r/SequelMemes/) are a
### Where?
-Blog post coming soon. Follow our [Twitter](twitter.com/supabase) to get notified immediately.
+Blog post coming soon. Follow our [Twitter](https://twitter.com/supabase) to get notified immediately.
diff --git a/www/components/CaseStudies/index.tsx b/www/components/CaseStudies/index.tsx
index 191db508e53..078192c5074 100644
--- a/www/components/CaseStudies/index.tsx
+++ b/www/components/CaseStudies/index.tsx
@@ -41,7 +41,7 @@ const CaseStudies = () => {
))}
-
+ {/* */}
)
}
diff --git a/www/components/ExampleCard.tsx b/www/components/ExampleCard.tsx
index da2286f3299..b937d6c678b 100644
--- a/www/components/ExampleCard.tsx
+++ b/www/components/ExampleCard.tsx
@@ -72,6 +72,15 @@ function ExampleCard(props: any) {
)}
+ {!props.demo_url && (
+
+
+ }>
+ View Code
+
+
+
+ )}
diff --git a/www/components/Nav/Developers.tsx b/www/components/Nav/Developers.tsx
index 1a48ee169cf..89c88745fa3 100644
--- a/www/components/Nav/Developers.tsx
+++ b/www/components/Nav/Developers.tsx
@@ -66,7 +66,7 @@ const Developers = () => {
diff --git a/www/data/Announcements.json b/www/data/Announcements.json
index d89a6d8d68b..4145701af75 100644
--- a/www/data/Announcements.json
+++ b/www/data/Announcements.json
@@ -1,12 +1,28 @@
[
{
"type": "Announcement",
- "title": "Beta launch",
- "description": "We're excited to announce we have moved from Alpha to Beta",
+ "title": "Launch Week II: The SQL",
+ "description": "We're launching a product every day for one week in July.",
"imgUrl": "images/case-study-monitoro.jpg",
"logoUrl": "images/logos/monitoro.png",
"organization": "Monitoro",
- "url": "/beta",
+ "url": "/blog/2021/07/22/supabase-launch-week-sql",
+ "postMeta": {
+ "name": "Paul Copplestone",
+ "avatarUrl": "https://avatars0.githubusercontent.com/u/10214025?v=4",
+ "publishDate": "Mar 16, 2020",
+ "readLength": 6
+ },
+ "ctaText": "View case study"
+ },
+ {
+ "type": "Announcement",
+ "title": "Supabase Storage",
+ "description": "Supabase launches Storage leveraging Postgres.",
+ "imgUrl": "images/case-study-monitoro.jpg",
+ "logoUrl": "images/logos/monitoro.png",
+ "organization": "Monitoro",
+ "url": "/blog/2021/03/30/supabase-storage",
"postMeta": {
"name": "Paul Copplestone",
"avatarUrl": "https://avatars0.githubusercontent.com/u/10214025?v=4",
diff --git a/www/data/DeveloperFeatures.json b/www/data/DeveloperFeatures.json
index c04d5e06ad7..0f6c00fe224 100644
--- a/www/data/DeveloperFeatures.json
+++ b/www/data/DeveloperFeatures.json
@@ -14,13 +14,13 @@
{
"name": "Local emulator",
"description": "Develop locally and push to production when you're ready",
- "badge": "Q1 2021",
- "url": "https://supabasesubscribe.vercel.app/subscription/localEmulator"
+ "badge": "",
+ "url": "https://supabase.io/docs/guides/self-hosting"
},
{
"name": "Supabase CLI",
"description": "Manage Supabase projects from your local machine",
- "badge": "Q2 2021",
- "url": "https://supabasesubscribe.vercel.app/subscription/commandLine"
+ "badge": "",
+ "url": "https://github.com/supabase/cli"
}
]
diff --git a/www/data/tweets/Tweets.json b/www/data/tweets/Tweets.json
index 1f0482436e0..a0da5737f65 100644
--- a/www/data/tweets/Tweets.json
+++ b/www/data/tweets/Tweets.json
@@ -1,21 +1,9 @@
[
{
- "text": "Been playing with @supabase_io and so far I'm liking it. I ❤️ you get full access to the database. It's very quick to get up and running, seems a nice fit for prototypes or smaller apps",
- "url": "https://twitter.com/mattegreer/status/1341765395133415425",
- "handle": "mattegreer",
- "img_url": "/images/twitter-profiles/IgXniSVk_400x400.jpg"
- },
- {
- "text": "Reply enjoying building on @supabase_io for a project with @lasnindrek - allows incredible development speed when bashing together an MVP",
- "url": "https://twitter.com/geordidearns/status/1344433863804788737",
- "handle": "geordidearns",
- "img_url": "/images/twitter-profiles/vppimY3M_400x400.jpg"
- },
- {
- "text": "OKAY OKAY FINE I'LL LOOK AT SUPABASE PROPERLY",
- "url": "https://twitter.com/benbarbersmith/status/1369758528764776453",
- "handle": "benbarbersmith",
- "img_url": "/images/twitter-profiles/Jqle_tE6_400x400.jpg"
+ "text": "DB/Backend: @supabase_io Reason: Takes care of Auth, DB hosting, API. Open source, easy to eject into something else. I love SQL. Cloud IDE is incredible for productivity.",
+ "url": "https://twitter.com/JessetheSibley/status/1369678185508593673",
+ "handle": "JessetheSibley",
+ "img_url": "/images/twitter-profiles/aNx9O0lH_400x400.jpg"
},
{
"text": "Been trying out this new stack. Loving it so far. Next.js (Frontend) + Stitches.js (Styling) + Radix (Primitives & Icons) + Supabase (Backend) + Vercel (Deployment) 😍 Thank you @vercel @Modulz @supabase_io for this amazing DX.",
@@ -24,16 +12,28 @@
"img_url": "/images/twitter-profiles/mZAIoZM1_400x400.jpg"
},
{
- "text": "DB/Backend: @supabase_io Reason: Takes care of Auth, DB hosting, API. Open source, easy to eject into something else. I love SQL. Cloud IDE is incredible for productivity.",
- "url": "https://twitter.com/JessetheSibley/status/1369678185508593673",
- "handle": "JessetheSibley",
- "img_url": "/images/twitter-profiles/aNx9O0lH_400x400.jpg"
+ "text": "OKAY OKAY FINE I'LL LOOK AT SUPABASE PROPERLY",
+ "url": "https://twitter.com/benbarbersmith/status/1369758528764776453",
+ "handle": "benbarbersmith",
+ "img_url": "/images/twitter-profiles/Jqle_tE6_400x400.jpg"
},
{
- "text": "Looking for a Firebase alternative? Consider using the open-source @supabase_io. It's as simple as Firebase but I found the dashboard UX more intuitive and hey it's OSS! It's in beta so you can use it for free rn.",
- "url": "https://twitter.com/mneelansh15/status/1368171468153118721",
- "handle": "mneelansh15",
- "img_url": "/images/twitter-profiles/6XKH3Rm6_400x400.jpg"
+ "text": "@supabase_io is amazing, super easy to get up and running quickly + they have multiple examples with various frameworks (chat app, auth, etc)",
+ "url": "https://twitter.com/ssw1ss/status/1366515168260337671",
+ "handle": "ssw1ss",
+ "img_url": "/images/twitter-profiles/4mD0C5Wk_400x400.jpg"
+ },
+ {
+ "text": "Been playing with @supabase_io and so far I'm liking it. I ❤️ you get full access to the database. It's very quick to get up and running, seems a nice fit for prototypes or smaller apps",
+ "url": "https://twitter.com/mattegreer/status/1341765395133415425",
+ "handle": "mattegreer",
+ "img_url": "/images/twitter-profiles/IgXniSVk_400x400.jpg"
+ },
+ {
+ "text": "Using @supabase_io with SWR by @vercel is something else 🔥",
+ "url": "https://twitter.com/eunjae_lee/status/1369788285317419009",
+ "handle": "eunjae_lee",
+ "img_url": "/images/twitter-profiles/eGjSYbQd_400x400.jpg"
},
{
"text": "big props to the @supabase_io team - open source is a two way street and they’re super responsive & community oriented",
@@ -41,6 +41,12 @@
"handle": "_mateomorris",
"img_url": "/images/twitter-profiles/1FyEWRE8_400x400.jpg"
},
+ {
+ "text": "Enjoying building on @supabase_io for a project with @lasnindrek - allows incredible development speed when bashing together an MVP",
+ "url": "https://twitter.com/geordidearns/status/1344433863804788737",
+ "handle": "geordidearns",
+ "img_url": "/images/twitter-profiles/vppimY3M_400x400.jpg"
+ },
{
"text": "Been actively working on Quill. It's been rapid, thanks to @supabase_io and @tailwindui",
"url": "https://twitter.com/quill_web/status/1369061125157961734",
@@ -53,12 +59,6 @@
"handle": "MartinOrszulik",
"img_url": "/images/twitter-profiles/ext7ggBo_400x400.jpg"
},
- {
- "text": "Using @supabase_io with SWR by @vercel is something else Fire",
- "url": "https://twitter.com/eunjae_lee/status/1369788285317419009",
- "handle": "eunjae_lee",
- "img_url": "/images/twitter-profiles/eGjSYbQd_400x400.jpg"
- },
{
"text": "Another month, another impressive @supabase_io release. It’s rare to see a startup ship in public so consistently.",
"url": "https://twitter.com/yuris/status/1366770633401491456",
@@ -71,12 +71,6 @@
"handle": "swyx",
"img_url": "/images/twitter-profiles/qhvO9V6x_400x400.jpg"
},
- {
- "text": "@supabase_io is amazing, super easy to get up and running quickly + they have multiple examples with various frameworks (chat app, auth, etc)",
- "url": "https://twitter.com/ssw1ss/status/1366515168260337671",
- "handle": "ssw1ss",
- "img_url": "/images/twitter-profiles/4mD0C5Wk_400x400.jpg"
- },
{
"text": "This weekend I made a personal record 🥇 on the less time spent creating an application with social login / permissions, database, cdn, infinite scaling, git push to deploy and for free. Thanks to @supabase_io and @vercel",
"url": "https://twitter.com/jperelli/status/1366195769657720834",
@@ -107,6 +101,12 @@
"handle": "prakis",
"img_url": "/images/twitter-profiles/eRvqp-t0_400x400.jpg"
},
+ {
+ "text": "Looking for a Firebase alternative? Consider using the open-source @supabase_io. It's as simple as Firebase but I found the dashboard UX more intuitive and hey it's OSS! It's in beta so you can use it for free rn.",
+ "url": "https://twitter.com/mneelansh15/status/1368171468153118721",
+ "handle": "mneelansh15",
+ "img_url": "/images/twitter-profiles/6XKH3Rm6_400x400.jpg"
+ },
{
"text": "Me using @supabase_io for the first time right now 🤯",
"url": "https://twitter.com/nasiscoe/status/1365140856035024902",
diff --git a/www/pages/auth/Auth.tsx b/www/pages/auth/Auth.tsx
index 5302f45f424..f3c846e2195 100644
--- a/www/pages/auth/Auth.tsx
+++ b/www/pages/auth/Auth.tsx
@@ -118,20 +118,15 @@ function AuthPage() {
+
All the social providers
-
- Enable social logins with the click of a button. Google, Facebook, Github, Azure,
- Gitlab and Bitbucket.
+
+ Enable social logins with the click of a button. Google, Facebook, GitHub, Azure,
+ Gitlab, Twitter, Discord, and Bitbucket.
-
-
-
- Twitter coming soon
-
-
@@ -142,10 +137,10 @@ function AuthPage() {
Fully integrated
-
- Incredibly simple Auth, without a single external authentication service.
+
+ Incredibly simple Auth, without a single external authentication service. Built-in
+ Authentication, Authorization, and User Management.
-
Built-in Authentication, Authorization, and User Management.
@@ -156,11 +151,10 @@ function AuthPage() {
Own your data
-
+
User data stored in your Supabase database so you never have to worry about 3rd
- party privacy issues.
+ party privacy issues. Host your data in 8 different locations.
+
diff --git a/www/pages/database/Database.tsx b/www/pages/database/Database.tsx
index aa9482b40b2..df51e42e25d 100644
--- a/www/pages/database/Database.tsx
+++ b/www/pages/database/Database.tsx
@@ -155,7 +155,11 @@ function Database() {
-
+
+
+
+
+
Realtime enabled
diff --git a/www/pages/storage/Storage.tsx b/www/pages/storage/Storage.tsx
index 20289f61add..2e027bd8b14 100644
--- a/www/pages/storage/Storage.tsx
+++ b/www/pages/storage/Storage.tsx
@@ -35,7 +35,7 @@ function StoragePage() {
const meta_title = 'Storage | Store any digital content'
const meta_description =
- 'An open source Object store service with unlimited scalability, for any file type.'
+ 'An open source Object store with unlimited scalability, for any file type.'
return (
<>
@@ -64,7 +64,7 @@ function StoragePage() {
,
]}
subheader={[
- 'An open source Object store service with unlimited scalability, for any file type.',
+ 'An open source Object store with unlimited scalability, for any file type.',
'With custom policies and permissions that are familiar and easy to implement.',
]}
image={[
@@ -116,8 +116,14 @@ function StoragePage() {
-
-
+
+
+
+
Lightning fast
@@ -133,8 +139,14 @@ function StoragePage() {
}
- title="CDN integration"
+ title="CDN"
text="Serve from the edge to reduce latency."
/>
Coming soon
@@ -213,7 +225,7 @@ function StoragePage() {
}
- title="Auto transformation & optimization"
+ title="Transformations"
text="Resize and compress your media before you serve it."
/>
Coming soon
@@ -243,7 +255,7 @@ function StoragePage() {
with Supabase Auth
-
+
Using Postgres Row Level Security to create Object access rules.