PicoBase SDK Integration Guide

A step-by-step guide for adding PicoBase to your project. No backend experience required.


What You Get

PicoBase gives your app a complete backend with four building blocks:

Building blockWhat it does
AuthSign up, sign in, sign out, password reset, Google/GitHub login
DatabaseStore, read, update, and delete data (called "collections")
StorageUpload and serve files (images, PDFs, etc.)
RealtimeGet instant updates when data changes (no refreshing)

Before You Start

You need two things from the PicoBase dashboard:

  1. Your instance URL — looks like https://myapp.picobase.com
  2. Your API key — starts with pbk_ (e.g. pbk_abc12345_xxxxxxxxxxxxxxxx)

To get these:
1. Sign in at the PicoBase dashboard
2. Click Create Instance and give it a name
3. Once created, click into your instance
4. Click Create API Key and copy it somewhere safe — it is only shown once

You also need Node.js installed (version 18 or newer).

Step 1 — Install the SDK

Open your terminal, navigate to your project folder, and run:

bash
npm install @picobase_app/client

If you're using React, also install the React helpers:

bash
npm install @picobase_app/client @picobase_app/react

Step 2 — Create Your Client

The "client" is the object you use to talk to PicoBase. You create it once and use it everywhere.

For any JavaScript/TypeScript project

Create a file called picobase.ts (or picobase.js) wherever you keep your utility files:

src/lib/picobase.tstypescript
import { createClient } from '@picobase_app/client'

const pb = createClient(
  'https://myapp.picobase.com',   // <-- replace with your URL
  'pbk_abc12345_xxxxxxxxxxxxxxxx' // <-- replace with your API key
)

export default pb

For React projects (recommended)

Instead of importing the client everywhere, wrap your app with the PicoBaseProvider. This gives every component access to PicoBase through hooks.

src/main.tsxtsx
import { PicoBaseProvider } from '@picobase_app/react'

function App() {
  return (
    <PicoBaseProvider
      url="https://myapp.picobase.com"
      apiKey="pbk_abc12345_xxxxxxxxxxxxxxxx"
    >
      {/* Everything inside here can use PicoBase hooks */}
      <YourApp />
    </PicoBaseProvider>
  )
}

Keep your API key out of your code

Optional but recommended

Store your URL and key in a .env file so they don't end up in git:

.env.localbash
VITE_PICOBASE_URL=https://myapp.picobase.com
VITE_PICOBASE_API_KEY=pbk_abc12345_xxxxxxxxxxxxxxxx

Then reference them in your code:

typescript
// Vite projects
const pb = createClient(
  import.meta.env.VITE_PICOBASE_URL,
  import.meta.env.VITE_PICOBASE_API_KEY
)

// Next.js projects
const pb = createClient(
  process.env.NEXT_PUBLIC_PICOBASE_URL!,
  process.env.NEXT_PUBLIC_PICOBASE_API_KEY!
)

Next.js TIP: Name the variables with a NEXT_PUBLIC_ prefix to make them available in the browser.

Step 3 — Add User Authentication

Option A: Drop-in Auth Form (fastest)

Use the AuthForm component for a full login/signup form with zero config.

tsx
import { AuthForm } from '@picobase_app/react'

function LoginPage() {
  return (
    <AuthForm
      providers={['google', 'github']}
      onSuccess={(user) => {
        console.log('Logged in as', user.email)
        window.location.href = '/dashboard'
      }}
    />
  )
}

Option B: Build Your Own Login Form

Use the useAuth hook for full control.

Sign-up form

tsx
import { useState } from 'react'
import { useAuth } from '@picobase_app/react'

function SignUpForm() {
  const { signUp } = useAuth()
  const [email, setEmail] = useState('')
  const [password, setPassword] = useState('')

  async function handleSubmit(e) {
    e.preventDefault()
    await signUp(email, password)
    // Redirect or update UI
  }

  return (
    <form onSubmit={handleSubmit}>
      <input type="email" value={email} onChange={e => setEmail(e.target.value)} required />
      <input type="password" value={password} onChange={e => setPassword(e.target.value)} required />
      <button type="submit">Sign Up</button>
    </form>
  )
}

Step 4 — Protect Pages

Show different content depending on whether the user is logged in.

tsx
import { useAuth } from '@picobase_app/react'

function App() {
  const { user, loading } = useAuth()

  // Still checking if user is logged in
  if (loading) return <p>Loading...</p>

  // Not logged in
  if (!user) return <LoginPage />

  // Logged in
  return (
    <div>
      <p>Welcome, {user.email}!</p>
      <Dashboard />
    </div>
  )
}

Step 5 — Read and Write Data

Collections are like database tables. Create them in the dashboard, then use them in code.

React shortcut: useCollection hook

Fetches data and handles loading/error states automatically.

tsx
import { useCollection } from '@picobase_app/react'

function PostList() {
  const { items, loading, error } = useCollection('posts', {
    sort: '-created',
    filter: 'published = true',
  })

  if (loading) return <p>Loading posts...</p>
  if (error) return <p>Error: {error.message}</p>

  return (
    <ul>
      {items.map(post => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  )
}

CRUD Operations (Vanilla JS / Handlers)

typescript
// Create
const newPost = await pb.collection('posts').create({
  title: 'My First Post',
  published: true,
})

// Read (List)
const result = await pb.collection('posts').getList(1, 20, {
  sort: '-created',
})

// Update
await pb.collection('posts').update('RECORD_ID', {
  title: 'Updated Title',
})

// Delete
await pb.collection('posts').delete('RECORD_ID')

Step 6 — Upload Files

Files are attached to records. Add a file field to your collection in the dashboard first.

typescript
const formData = new FormData()
formData.append('title', 'My photo')
formData.append('image', fileInput.files[0])

const record = await pb.collection('posts').create(formData)

Get the file URL

typescript
const url = pb.storage.getFileUrl(record, 'photo.jpg')

// With thumbnail transform
const thumbUrl = pb.storage.getFileUrl(record, 'photo.jpg', {
  thumb: '100x100',
})

Step 7 — Listen for Live Updates

Realtime subscriptions let your app update instantly when data changes.

typescript
// Watch for any changes
const unsubscribe = await pb.collection('messages').subscribe((event) => {
  console.log(event.action) // 'create', 'update', or 'delete'
  console.log(event.record) // the data that changed
})

// Stop listening
await unsubscribe()

Full Working Example

A complete React app with auth and data — ready to copy.

src/App.tsxtsx
import { PicoBaseProvider, useAuth, useCollection, AuthForm } from '@picobase_app/react'

const URL = import.meta.env.VITE_PICOBASE_URL
const KEY = import.meta.env.VITE_PICOBASE_API_KEY

export default function App() {
  return (
    <PicoBaseProvider url={URL} apiKey={KEY}>
      <Main />
    </PicoBaseProvider>
  )
}

function Main() {
  const { user, loading, signOut } = useAuth()

  if (loading) return <p>Loading...</p>

  if (!user) {
    return (
      <div style={{ maxWidth: 400, margin: '100px auto' }}>
        <AuthForm
          providers={['google']}
          onSuccess={() => window.location.reload()}
        />
      </div>
    )
  }

  return (
    <div style={{ maxWidth: 600, margin: '40px auto' }}>
      <h1>Welcome, {user.email}</h1>
      <button onClick={signOut}>Sign Out</button>
      <hr />
      <PostsList />
    </div>
  )
}

function PostsList() {
  const { items, loading, error } = useCollection('posts', {
    sort: '-created',
    filter: 'published = true',
  })

  if (loading) return <p>Loading posts...</p>
  if (items.length === 0) return <p>No posts yet.</p>

  return (
    <ul>
      {items.map(post => (
        <li key={post.id}>
          <strong>{post.title}</strong>
          <p>{post.content}</p>
        </li>
      ))}
    </ul>
  )
}

Quick Reference

typescript
import { createClient } from '@picobase_app/client'
const pb = createClient(url, apiKey)

// Auth
await pb.auth.signUp({ email, password })
await pb.auth.signIn({ email, password })
await pb.auth.signInWithOAuth({ provider: 'google' })
await pb.auth.requestPasswordReset(email)
pb.auth.signOut()
pb.auth.user         // current user or null
pb.auth.isValid      // true if logged in
pb.auth.onStateChange((event, record) => { ... })

// Database
await pb.collection('x').getList(page, perPage, { filter, sort, expand })
await pb.collection('x').getOne(id)
await pb.collection('x').getFirstListItem(filter)
await pb.collection('x').getFullList({ filter, sort })
await pb.collection('x').create(data)
await pb.collection('x').update(id, data)
await pb.collection('x').delete(id)

// Realtime
const unsub = await pb.collection('x').subscribe(callback)
await unsub()

// Storage
pb.storage.getFileUrl(record, filename)
pb.storage.getFileUrl(record, filename, { thumb: '100x100' })

Common Mistakes

"usePicoBase* hooks must be used within a PicoBaseProvider"

Make sure <PicoBaseProvider> wraps your entire app or at least the part that uses the hooks.

"Instance unavailable after 3 retries"

Your PicoBase instance might be stopped. Check the dashboard. The SDK retries automatically, but if it wakes up too slowly, you might see this error.

Forgetting "await"

Most SDK calls are asynchronous. If you forget await, you get a Promise instead of data.

Next Steps

  • Create collections in the dashboard to define your data structure
  • Set up collection rules to control who can read/write data
  • Enable OAuth providers in your instance's auth settings
  • Real the full API reference in the @picobase_app/client README