@blinkdotnew/sdk
Version:
Blink TypeScript SDK for client-side applications - Zero-boilerplate CRUD + auth + AI + analytics + notifications for modern SaaS/AI apps
1,485 lines (1,228 loc) β’ 83.6 kB
Markdown
# @blinkdotnew/sdk
[](https://badge.fury.io/js/%40blinkdotnew%2Fsdk)
[](https://www.typescriptlang.org/)
**The full-stack TypeScript SDK that powers Blink AI-generated apps**
Blink is an AI App Builder that builds fully functional apps in seconds. This SDK (`@blinkdotnew/sdk`) is the TypeScript foundation that powers every Blink app natively, providing zero-boilerplate authentication, database operations, AI capabilities, and file storage. Works seamlessly on both client-side (React, Vue, etc.) and server-side (Node.js, Deno, Edge functions).
## π Quick Start
### **Step 1: Create a Blink Project**
Visit [blink.new](https://blink.new) and create a new project. Blink's AI agent will build your app in seconds.
### **Step 2: Install the SDK**
Use Blink's AI agent to automatically install this SDK in your Vite React TypeScript client, or install manually:
```bash
npm install @blinkdotnew/sdk
```
### **Step 3: Use Your Project ID**
Get your project ID from your Blink dashboard and start building:
```typescript
import { createClient } from '@blinkdotnew/sdk'
const blink = createClient({
projectId: 'your-blink-project-id', // From blink.new dashboard
authRequired: false // Don't force immediate auth - let users browse first
})
// Authentication - Choose your mode:
// π― MANAGED MODE: Quick setup with hosted auth page
const blink = createClient({
projectId: 'your-project',
auth: { mode: 'managed' }
})
// Use: blink.auth.login() - redirects to blink.new auth
// π¨ HEADLESS MODE: Custom UI with full control
const blink = createClient({
projectId: 'your-project',
auth: { mode: 'headless' }
})
// Use: blink.auth.signInWithEmail(), blink.auth.signInWithGoogle(), etc.
// Current user (works in both modes)
const user = await blink.auth.me()
// Database operations (zero config)
const todos = await blink.db.todos.list({
where: { userId: user.id },
orderBy: { createdAt: 'desc' },
limit: 20
})
// AI operations (native)
const { text } = await blink.ai.generateText({
prompt: "Write a summary of the user's todos"
})
// Data operations (extract text from documents)
const text = await blink.data.extractFromUrl("https://example.com/document.pdf")
// Website scraping and screenshots (crystal clear results!)
const { markdown, metadata, links } = await blink.data.scrape("https://competitor.com")
const screenshotUrl = await blink.data.screenshot("https://competitor.com")
// Web search (get real-time information)
const searchResults = await blink.data.search("chatgpt latest news", { type: 'news' })
const localResults = await blink.data.search("best restaurants", { location: "San Francisco,CA,United States" })
// Notifications (NEW!)
const { success } = await blink.notifications.email({
to: 'customer@example.com',
subject: 'Your order has shipped!',
html: '<h1>Order Confirmation</h1><p>Your order #12345 is on its way.</p>'
})
// Secure API proxy (call external APIs with secret substitution)
const response = await blink.data.fetch({
url: "https://api.sendgrid.com/v3/mail/send",
method: "POST",
headers: { "Authorization": "Bearer {{sendgrid_api_key}}" },
body: { /* email data */ }
})
// Realtime operations (live messaging and presence)
const unsubscribe = await blink.realtime.subscribe('chat-room', (message) => {
console.log('New message:', message.data)
})
await blink.realtime.publish('chat-room', 'message', { text: 'Hello world!' })
// Get presence - returns array of PresenceUser objects directly
const users = await blink.realtime.presence('chat-room')
console.log('Online users:', users.length)
// users is PresenceUser[] format:
// [
// {
// userId: 'user123',
// metadata: { displayName: 'Alice', status: 'online' },
// joinedAt: 1640995200000,
// lastSeen: 1640995230000
// }
// ]
// Analytics operations (automatic pageview tracking + custom events)
// Pageviews are tracked automatically on initialization and route changes
blink.analytics.log('button_clicked', {
button_id: 'signup',
page: '/pricing'
})
// Check if analytics is enabled
if (blink.analytics.isEnabled()) {
console.log('Analytics is active')
}
// Disable/enable analytics
blink.analytics.disable()
blink.analytics.enable()
// Storage operations (instant - returns public URL directly)
const { publicUrl } = await blink.storage.upload(
file,
`avatars/${user.id}-${Date.now()}.${file.name.split('.').pop()}`, // β
Extract original extension
{ upsert: true }
)
```
## π€ What is Blink?
**Blink is an AI App Builder** that creates fully functional applications in seconds. Simply describe what you want to build, and Blink's AI agent will:
- ποΈ **Generate complete apps** with React + TypeScript + Vite
- π§ **Auto-install this SDK** with zero configuration
- π¨ **Create beautiful UIs** with Tailwind CSS
- π **Deploy instantly** with authentication, database, AI, and storage built-in
## π SDK Features
This SDK powers every Blink-generated app with:
- **π Authentication**: Flexible auth system with managed (redirect) and headless (custom UI) modes, email/password, social providers (Google, GitHub, Apple, Microsoft), magic links, RBAC, and custom email branding
- **ποΈ Database**: PostgREST-compatible CRUD operations with advanced filtering
- **π€ AI**: Multi-model image generation & editing (10 models), text generation with web search, object generation, speech synthesis, and transcription
- **π Data**: Extract text content from documents, secure API proxy with secret substitution, web scraping, screenshots, and web search
- **π Storage**: File upload, download, and management
- **π§ Notifications**: Email sending with attachments, custom branding, and delivery tracking
- **β‘ Realtime**: WebSocket-based pub/sub messaging, presence tracking, and live updates
- **π Analytics**: Automatic pageview tracking, custom event logging, session management, and privacy-first design
- **π Universal**: Works on client-side and server-side
- **π± Framework Agnostic**: React, Vue, Svelte, vanilla JS, Node.js, Deno, **React Native**
- **π± React Native**: First-class mobile support with AsyncStorage integration and platform-aware features
- **π Real-time**: Built-in auth state management and token refresh
- **β‘ Zero Boilerplate**: Everything works out of the box
## π οΈ Manual Installation & Setup
> **π‘ Tip**: If you're using Blink's AI agent, this is all done automatically for you!
### Client-side (React, Vue, etc.)
```typescript
import { createClient } from '@blinkdotnew/sdk'
const blink = createClient({
projectId: 'your-blink-project-id', // From blink.new dashboard
authRequired: false, // Let users browse first - require auth only for protected areas
auth: {
mode: 'managed' // Use new explicit configuration
}
})
```
> **β οΈ Version Requirement**: The flexible authentication system (managed vs headless modes) requires SDK version **0.18.0 or higher**. If you're using version 0.17.x or below, you'll only have access to the legacy authentication system. Please upgrade to access the new authentication features.
### Server-side (Node.js, Deno, Edge functions)
```typescript
import { createClient } from '@blinkdotnew/sdk'
const blink = createClient({
projectId: 'your-blink-project-id', // From blink.new dashboard
auth: { mode: 'managed' } // Manual token management
})
// Token injection is only needed when calling blink.auth.* methods on the server
```
### π± React Native (iOS & Android)
The SDK has **first-class React Native support** with platform-aware features that automatically adapt to mobile environments.
#### Step 1: Install Dependencies
```bash
npm install @blinkdotnew/sdk @react-native-async-storage/async-storage expo-web-browser
```
**Required packages:**
- `@blinkdotnew/sdk` - The Blink SDK
- `@react-native-async-storage/async-storage` - For token persistence
- `expo-web-browser` - For OAuth authentication (Google, GitHub, Apple, etc.)
#### Step 2: Create Client (`lib/blink.ts`)
**β οΈ IMPORTANT: You MUST pass `webBrowser: WebBrowser` to enable OAuth on mobile!**
```typescript
// lib/blink.ts
import { createClient, AsyncStorageAdapter } from '@blinkdotnew/sdk'
import AsyncStorage from '@react-native-async-storage/async-storage'
import * as WebBrowser from 'expo-web-browser' // β Import this
export const blink = createClient({
projectId: 'your-project-id',
authRequired: false,
auth: {
mode: 'headless',
webBrowser: WebBrowser // β Pass it here! Required for OAuth
},
storage: new AsyncStorageAdapter(AsyncStorage)
})
```
#### Step 3: Use Authentication
**Email/Password (works immediately):**
```typescript
// Sign up
const user = await blink.auth.signUp({
email: 'user@example.com',
password: 'SecurePass123'
})
// Sign in
const user = await blink.auth.signInWithEmail('user@example.com', 'SecurePass123')
```
**OAuth (Google, GitHub, Apple, Microsoft):**
```typescript
// β
Same code works on web, iOS, AND Android!
const user = await blink.auth.signInWithGoogle()
const user = await blink.auth.signInWithGitHub()
const user = await blink.auth.signInWithApple()
const user = await blink.auth.signInWithMicrosoft()
```
#### Step 4: Create Auth Hook (`hooks/useAuth.ts`)
```typescript
// hooks/useAuth.ts
import { useEffect, useState } from 'react'
import { blink } from '@/lib/blink'
import type { BlinkUser } from '@blinkdotnew/sdk'
export function useAuth() {
const [user, setUser] = useState<BlinkUser | null>(null)
const [isLoading, setIsLoading] = useState(true)
useEffect(() => {
const unsubscribe = blink.auth.onAuthStateChanged((state) => {
setUser(state.user)
setIsLoading(state.isLoading)
})
return unsubscribe
}, [])
return {
user,
isLoading,
isAuthenticated: !!user,
signInWithGoogle: () => blink.auth.signInWithGoogle(),
signInWithGitHub: () => blink.auth.signInWithGitHub(),
signInWithApple: () => blink.auth.signInWithApple(),
signOut: () => blink.auth.signOut(),
}
}
```
#### Step 5: Use in Components
```typescript
import { useAuth } from '@/hooks/useAuth'
import { View, Text, Button } from 'react-native'
function App() {
const { user, isLoading, signInWithGoogle, signOut } = useAuth()
if (isLoading) return <Text>Loading...</Text>
if (!user) {
return <Button onPress={signInWithGoogle} title="Sign in with Google" />
}
return (
<View>
<Text>Welcome, {user.email}!</Text>
<Button onPress={signOut} title="Sign Out" />
</View>
)
}
```
#### Common Mistakes
```typescript
// β WRONG: Missing webBrowser - OAuth won't work on mobile!
const blink = createClient({
projectId: 'your-project-id',
auth: { mode: 'headless' }, // Missing webBrowser!
storage: new AsyncStorageAdapter(AsyncStorage)
})
// β
CORRECT: Include webBrowser for OAuth support
import * as WebBrowser from 'expo-web-browser'
const blink = createClient({
projectId: 'your-project-id',
auth: {
mode: 'headless',
webBrowser: WebBrowser // β Required for OAuth!
},
storage: new AsyncStorageAdapter(AsyncStorage)
})
```
#### Low-Level OAuth (For Custom Control)
If you need custom polling timeouts or manual browser handling:
```typescript
// Get auth URL and authenticate function separately
const { authUrl, authenticate } = await blink.auth.signInWithProviderMobile('google')
// Open browser manually
await WebBrowser.openAuthSessionAsync(authUrl)
// Poll with custom options
const user = await authenticate({
maxAttempts: 120, // 60 seconds (default: 60 = 30 seconds)
intervalMs: 500 // Check every 500ms
})
```
#### Platform Features
- β
**AsyncStorage** - Secure token persistence
- β
**Universal OAuth** - Same code works on web + mobile
- β
**expo-web-browser** - Native browser UI
- β
**No deep linking** - Session-based polling
- β
**Works in Expo Go** - No custom dev client needed
- β
**Auto token refresh** - Seamless sessions
## π API Reference
### Authentication
> **β οΈ Version Requirement**: The flexible authentication system requires SDK version **0.18.0 or higher**. Version 0.17.x and below only support the legacy authentication system.
Blink provides **two authentication modes**:
## π― Managed Mode (Redirect-based)
**Perfect for:** Quick setup, minimal code
**Best for:** Websites, simple apps, MVP development
```typescript
const blink = createClient({
projectId: 'your-project',
auth: { mode: 'managed' }
})
// ONE METHOD: Redirect to hosted auth page
blink.auth.login() // β Redirects to blink.new/auth
blink.auth.logout() // Clear tokens and redirect
// User state (automatic after redirect)
const user = await blink.auth.me()
```
## π¨ Headless Mode (Custom UI)
**Perfect for:** Custom branding, advanced UX, mobile apps
**Best for:** Production apps, branded experiences
```typescript
const blink = createClient({
projectId: 'your-project',
auth: { mode: 'headless' }
})
// MULTIPLE METHODS: Build your own UI
const user = await blink.auth.signUp({ email, password })
const user = await blink.auth.signInWithEmail(email, password)
const user = await blink.auth.signInWithGoogle()
const user = await blink.auth.signInWithGitHub()
const user = await blink.auth.signInWithApple()
const user = await blink.auth.signInWithMicrosoft()
// β
Store custom signup fields inside metadata
await blink.auth.signUp({
email: 'founder@example.com',
password: 'SuperSecret123',
displayName: 'Alex Founder',
role: 'operations',
metadata: {
company: 'Acme Freight',
marketingConsent: true
}
})
// `displayName`, `avatar`, and `role` map to dedicated auth columns.
// Everything else goes into auth.users.metadata automatically.
// Keep custom fields in metadata or your own profile tableβavoid adding NOT NULL
// columns directly to auth tables.
// Magic links (passwordless)
await blink.auth.sendMagicLink(email)
// Password management
await blink.auth.sendPasswordResetEmail(email)
await blink.auth.sendPasswordResetEmail(email, {
redirectUrl: 'https://myapp.com/reset-password'
})
await blink.auth.changePassword(oldPass, newPass)
// Email verification
await blink.auth.sendEmailVerification()
```
### β‘ Quick Mode Comparison
| Feature | **Managed Mode** | **Headless Mode** |
|---------|------------------|-------------------|
| **Setup** | 1 line of code | Custom UI required |
| **Methods** | `login()` only | `signInWith*()` methods |
| **UI** | Hosted auth page | Your custom forms |
| **Branding** | Blink-branded | Fully customizable |
| **Mobile** | Web redirects | Native integration |
### π¨ **Common Mistake**
```typescript
// β WRONG: Using managed method in headless mode
const blink = createClient({
auth: { mode: 'headless' }
})
blink.auth.login() // Still redirects! Wrong method for headless
// β
CORRECT: Use headless methods
await blink.auth.signInWithEmail(email, password)
await blink.auth.signInWithGoogle()
```
#### π§ Provider Configuration
**Step 1: Enable Providers in Your Project**
1. Go to [blink.new](https://blink.new) and open your project
2. Navigate to **Project β Workspace β Authentication**
3. Toggle providers on/off:
- **Email** β
(enabled by default) - Includes email/password, magic links, and verification
- **Google** β
(enabled by default)
- **GitHub** βͺ (disabled by default)
- **Apple** βͺ (disabled by default)
- **Microsoft** βͺ (disabled by default)
4. Configure email settings:
- **Require email verification**: Off by default (easier implementation)
- **Allow user signup**: On by default
**Step 2: Discover Available Providers**
```typescript
// Get providers enabled for your project
const availableProviders = await blink.auth.getAvailableProviders()
// Returns: ['email', 'google'] (based on your project settings)
// Use in your UI to show only enabled providers
const showGoogleButton = availableProviders.includes('google')
const showGitHubButton = availableProviders.includes('github')
```
**Step 3: Client-Side Filtering (Headless Mode)**
```typescript
const blink = createClient({
projectId: 'your-project',
auth: {
mode: 'headless'
// All providers controlled via project settings
}
})
```
**Managed Mode: Automatic Provider Display**
```typescript
const blink = createClient({
projectId: 'your-project',
auth: { mode: 'managed' }
})
// The hosted auth page automatically shows only enabled providers
blink.auth.login() // Shows Email + Google by default
```
#### π§ Email Verification Flow
**By default, email verification is NOT required** for easier implementation. Enable it only if needed:
**Step 1: Configure Verification (Optional)**
1. Go to [blink.new](https://blink.new) β Project β Workspace β Authentication
2. Toggle **"Require email verification"** ON (disabled by default)
**Step 2: Handle the Complete Flow**
```typescript
// User signup - always send verification email for security
try {
const user = await blink.auth.signUp({ email, password })
await blink.auth.sendEmailVerification()
setMessage('Account created! Check your email to verify.')
} catch (error) {
setError(error.message)
}
// User signin - handle verification requirement
try {
await blink.auth.signInWithEmail(email, password)
// Success - user is signed in
} catch (error) {
if (error.code === 'EMAIL_NOT_VERIFIED') {
setError('Please verify your email first')
await blink.auth.sendEmailVerification() // Resend verification
} else {
setError(error.message)
}
}
// Manual verification resend
await blink.auth.sendEmailVerification()
```
**What Happens:**
1. **Signup**: User account created, `email_verified = false`
2. **Verification Email**: User clicks link β `email_verified = true`
3. **Signin Check**: If verification required AND not verified β `EMAIL_NOT_VERIFIED` error
4. **Success**: User can sign in once verified (or if verification not required)
#### Flexible Email System
**Maximum flexibility** - you control email branding while Blink handles secure tokens:
```typescript
// Option A: Custom email delivery (full branding control)
const resetData = await blink.auth.generatePasswordResetToken('user@example.com')
// Returns: { token, expiresAt, resetUrl }
// Send with your email service and branding
await yourEmailService.send({
to: 'user@example.com',
subject: 'Reset your YourApp password',
html: `
<div style="font-family: Arial, sans-serif;">
<img src="https://yourapp.com/logo.png" alt="YourApp" />
<h1>Reset Your Password</h1>
<a href="${resetData.resetUrl}"
style="background: #0070f3; color: white; padding: 16px 32px;
text-decoration: none; border-radius: 8px;">
Reset Password
</a>
</div>
`
})
// Option B: Blink default email (zero setup)
await blink.auth.sendPasswordResetEmail('user@example.com')
// Same flexibility for email verification and magic links
const verifyData = await blink.auth.generateEmailVerificationToken()
const magicData = await blink.auth.generateMagicLinkToken('user@example.com')
```
#### Role-Based Access Control (RBAC)
```typescript
// Configure roles and permissions
const blink = createClient({
projectId: 'your-project',
auth: {
mode: 'headless',
roles: {
admin: { permissions: ['*'] },
editor: { permissions: ['posts.create', 'posts.update'] },
viewer: { permissions: ['posts.read'] }
}
}
})
// Check permissions
const canEdit = blink.auth.can('posts.update')
const isAdmin = blink.auth.hasRole('admin')
const isStaff = blink.auth.hasRole(['admin', 'editor'])
// Use in components
function EditButton() {
if (!blink.auth.can('posts.update')) return null
return <button onClick={editPost}>Edit Post</button>
}
```
#### Core Methods
> β οΈ Tokens are managed automatically for Blink APIs. Use `getValidToken()` only if you must manually pass a token to your own backend or third-party services.
```typescript
// User management
const user = await blink.auth.me()
await blink.auth.updateMe({ displayName: 'New Name' })
// Token management
blink.auth.setToken(jwt, persist?)
const isAuth = blink.auth.isAuthenticated()
const token = await blink.auth.getValidToken() // Get valid token (auto-refreshes)
// Password management
await blink.auth.sendPasswordResetEmail('user@example.com')
await blink.auth.sendPasswordResetEmail('user@example.com', {
redirectUrl: 'https://myapp.com/reset-password' // Custom reset page
})
await blink.auth.changePassword('oldPass', 'newPass')
await blink.auth.confirmPasswordReset(token, newPassword)
// Email verification
await blink.auth.verifyEmail(token)
// Provider discovery
const providers = await blink.auth.getAvailableProviders()
// Auth state listener (REQUIRED for React apps!)
const unsubscribe = blink.auth.onAuthStateChanged((state) => {
console.log('Auth state:', state)
// state.user - current user or null
// state.isLoading - true while auth is initializing
// state.isAuthenticated - true if user is logged in
// state.tokens - current auth tokens
})
```
#### Login Redirect Behavior
When `login()` is called, the SDK automatically determines where to redirect after authentication:
```typescript
// Automatic redirect (uses current page URL)
blink.auth.login()
// β Redirects to: blink.new/auth?redirect_url=https://yourapp.com/current-page
// Custom redirect URL
blink.auth.login('https://yourapp.com/dashboard')
// β Redirects to: blink.new/auth?redirect_url=https://yourapp.com/dashboard
// Manual login button example
const handleLogin = () => {
// The SDK will automatically use the current page URL
blink.auth.login()
// Or specify a custom redirect
// blink.auth.login('https://yourapp.com/welcome')
}
```
**β
Fixed in v1.x**: The SDK now ensures redirect URLs are always absolute, preventing broken redirects when `window.location.href` returns relative paths.
### Database Operations
**π NEW: Automatic Case Conversion!**
The SDK now automatically converts between JavaScript camelCase and SQL snake_case:
- **Table names**: `blink.db.emailDrafts` β `email_drafts` table
- **Field names**: `userId`, `createdAt`, `isCompleted` β `user_id`, `created_at`, `is_completed`
- **No manual conversion needed!**
**β οΈ Important: Always Use camelCase in Your Code**
- β
**Correct**: `blink.db.emailDrafts.create({ userId: user.id, createdAt: new Date() })`
- β **Wrong**: `blink.db.email_drafts.create({ user_id: user.id, created_at: new Date() })`
```typescript
// Create (ID auto-generated if not provided)
const todo = await blink.db.todos.create({
id: 'todo_12345', // Optional - auto-generated if not provided
title: 'Learn Blink SDK',
userId: user.id, // camelCase in code
createdAt: new Date(), // camelCase in code
isCompleted: false // camelCase in code
})
// Read with filtering - returns camelCase fields
const todos = await blink.db.todos.list({
where: {
AND: [
{ userId: user.id }, // camelCase in filters
{ OR: [{ status: 'open' }, { priority: 'high' }] }
]
},
orderBy: { createdAt: 'desc' }, // camelCase in orderBy
limit: 20
})
// `todos` is a direct array: Todo[]
// Note: Boolean fields are returned as "0"/"1" strings from SQLite
// Check boolean values using Number(value) > 0
const completedTodos = todos.filter(todo => Number(todo.isCompleted) > 0)
const incompleteTodos = todos.filter(todo => Number(todo.isCompleted) === 0)
// Update
await blink.db.todos.update(todo.id, { isCompleted: true })
// Delete
await blink.db.todos.delete(todo.id)
// Bulk operations (IDs auto-generated if not provided)
await blink.db.todos.createMany([
{ title: 'Task 1', userId: user.id }, // ID will be auto-generated
{ id: 'custom_id', title: 'Task 2', userId: user.id } // Custom ID provided
])
await blink.db.todos.upsertMany([...])
```
### AI Operations
```typescript
// Text generation (simple prompt)
const { text } = await blink.ai.generateText({
prompt: 'Write a poem about coding',
maxTokens: 150
})
// Web search (OpenAI only) - get real-time information
const { text, sources } = await blink.ai.generateText({
prompt: 'Who is the current US president?',
search: true // Returns current info + source URLs
})
// Multi-step reasoning - for complex analysis
const { text } = await blink.ai.generateText({
prompt: 'Research and analyze tech trends',
search: true,
maxSteps: 10, // Override default (25 when tools used)
experimental_continueSteps: true // Override default (true when tools used)
})
// Text generation with image content
// β οΈ IMPORTANT: Images must be HTTPS URLs with file extensions (.jpg, .jpeg, .png, .gif, .webp)
// For file uploads, use blink.storage.upload() first to get public HTTPS URLs
// π« CRITICAL: When uploading files, NEVER hardcode extensions - use file.name or auto-detection
const { text } = await blink.ai.generateText({
messages: [
{
role: "user",
content: [
{ type: "text", text: "What do you see in this image?" },
{ type: "image", image: "https://storage.googleapis.com/.../.../photo.jpg" }
]
}
]
})
// Mixed content with multiple images
const { text } = await blink.ai.generateText({
messages: [
{
role: "user",
content: [
{ type: "text", text: "Compare these two images:" },
{ type: "image", image: "https://storage.googleapis.com/.../.../image1.jpg" },
{ type: "image", image: "https://cdn.example.com/image2.png" }
]
}
]
})
// Structured object generation
const { object } = await blink.ai.generateObject({
prompt: 'Generate a user profile',
schema: {
type: 'object',
properties: {
name: { type: 'string' },
age: { type: 'number' }
}
}
})
// β οΈ IMPORTANT: Schema Rule for generateObject()
// The top-level schema MUST use type: "object" - you cannot use type: "array" at the top level
// This ensures clear, robust, and extensible API calls with named parameters
// β
Correct: Array inside object
const { object: todoList } = await blink.ai.generateObject({
prompt: 'Generate a list of 5 daily tasks',
schema: {
type: 'object',
properties: {
tasks: {
type: 'array',
items: {
type: 'object',
properties: {
title: { type: 'string' },
priority: { type: 'string', enum: ['low', 'medium', 'high'] }
}
}
}
},
required: ['tasks']
}
})
// Result: { tasks: [{ title: "Exercise", priority: "high" }, ...] }
// β Wrong: Top-level array (will fail)
// const { object } = await blink.ai.generateObject({
// prompt: 'Generate tasks',
// schema: {
// type: 'array', // β This will throw an error
// items: { type: 'string' }
// }
// })
// Error: "schema must be a JSON Schema of 'type: \"object\"', got 'type: \"array\"'"
// Generate and modify images with AI - Multi-Model Support (10 models available)
// π₯ Choose between fast generation or high-quality results
// π¨ For style transfer: provide ALL images in the images array, don't reference URLs in prompts
// Basic image generation (uses default fast model: fal-ai/nano-banana)
const { data } = await blink.ai.generateImage({
prompt: 'A serene landscape with mountains and a lake at sunset'
})
console.log('Image URL:', data[0].url)
// High-quality generation with Pro model
const { data: proImage } = await blink.ai.generateImage({
prompt: 'A detailed infographic about AI with charts and diagrams',
model: 'fal-ai/nano-banana-pro', // High quality model
n: 1,
size: '1792x1024' // Custom size
})
// Generate multiple variations
const { data } = await blink.ai.generateImage({
prompt: 'A futuristic robot in different poses',
model: 'fal-ai/nano-banana', // Fast model
n: 3
})
data.forEach((img, i) => console.log(`Image ${i+1}:`, img.url))
**Available Models for Text-to-Image:**
| Model | Speed | Quality | Best For |
|-------|-------|---------|----------|
| `fal-ai/nano-banana` (default) | β‘ Fast | Good | Prototypes, high-volume generation |
| `fal-ai/nano-banana-pro` | Standard | β Excellent | Marketing materials, high-fidelity visuals |
| `fal-ai/gemini-25-flash-image` | β‘ Fast | Good | Alias for `nano-banana` |
| `fal-ai/gemini-3-pro-image-preview` | Standard | β Excellent | Alias for `nano-banana-pro` |
| `gemini-2.5-flash-image-preview` | β‘ Fast | Good | Legacy - Direct Gemini API |
| `gemini-3-pro-image-preview` | Standard | β Excellent | Legacy - Direct Gemini API |
// Image editing - transform existing images with prompts (uses default fast model)
const { data: headshots } = await blink.ai.modifyImage({
images: ['https://storage.example.com/user-photo.jpg'], // Up to 16 images supported!
prompt: 'Transform into professional business headshot with studio lighting'
})
// High-quality editing with Pro model
const { data: proEdited } = await blink.ai.modifyImage({
images: ['https://storage.example.com/portrait.jpg'],
prompt: 'Add a majestic ancient tree in the background with glowing leaves',
model: 'fal-ai/nano-banana-pro/edit' // High quality editing
})
// Advanced image editing with multiple input images
const { data } = await blink.ai.modifyImage({
images: [
'https://storage.example.com/photo1.jpg',
'https://storage.example.com/photo2.jpg',
'https://storage.example.com/photo3.jpg'
],
prompt: 'Combine these architectural styles into a futuristic building design',
model: 'fal-ai/nano-banana/edit', // Fast editing
n: 2
})
**Available Models for Image Editing:**
| Model | Speed | Quality | Best For |
|-------|-------|---------|----------|
| `fal-ai/nano-banana/edit` (default) | β‘ Fast | Good | Quick adjustments, style transfers |
| `fal-ai/nano-banana-pro/edit` | Standard | β Excellent | Detailed retouching, complex edits |
| `fal-ai/gemini-25-flash-image/edit` | β‘ Fast | Good | Alias for `nano-banana/edit` |
| `fal-ai/gemini-3-pro-image-preview/edit` | Standard | β Excellent | Alias for `nano-banana-pro/edit` |
// π¨ Style Transfer & Feature Application
// β οΈ IMPORTANT: When applying styles/features from one image to another,
// provide ALL images in the array - don't reference images in the prompt text
// β WRONG - Don't reference images in prompt
const wrongWay = await blink.ai.modifyImage({
images: [userPhotoUrl],
prompt: `Apply this hairstyle from the reference image to the person's head. Reference hairstyle: ${hairstyleUrl}`
})
// β
CORRECT - Provide all images in the array
const { data } = await blink.ai.modifyImage({
images: [userPhotoUrl, hairstyleUrl], // Both images provided
prompt: 'Apply the hairstyle from the second image to the person in the first image. Blend naturally with face shape.'
})
// More style transfer examples
const { data } = await blink.ai.modifyImage({
images: [portraitUrl, artworkUrl],
prompt: 'Apply the artistic style and color palette from the second image to the portrait in the first image'
})
const { data } = await blink.ai.modifyImage({
images: [roomPhotoUrl, designReferenceUrl],
prompt: 'Redesign the room in the first image using the interior design style shown in the second image'
})
// Multiple reference images for complex transformations
const { data } = await blink.ai.modifyImage({
images: [
originalPhotoUrl,
lightingReferenceUrl,
colorPaletteReferenceUrl,
compositionReferenceUrl
],
prompt: 'Transform the first image using the lighting style from image 2, color palette from image 3, and composition from image 4'
})
// π± File Upload + Style Transfer
// β οΈ Extract file extension properly - never hardcode .jpg/.png
// β WRONG - Hardcoded extension
const userUpload = await blink.storage.upload(file, `photos/${Date.now()}.jpg`) // Breaks HEIC/PNG files
// β
CORRECT - Extract original extension
const userUpload = await blink.storage.upload(
userPhoto.file,
`photos/${Date.now()}.${userPhoto.file.name.split('.').pop()}`
)
const hairstyleUpload = await blink.storage.upload(
hairstylePhoto.file,
`haircuts/${Date.now()}.${hairstylePhoto.file.name.split('.').pop()}`
)
const { data } = await blink.ai.modifyImage({
images: [userUpload.publicUrl, hairstyleUpload.publicUrl],
prompt: 'Apply hairstyle from second image to person in first image'
})
// Speech synthesis
const { url } = await blink.ai.generateSpeech({
text: 'Hello, world!',
voice: 'nova'
})
// Audio transcription - Multiple input formats supported
// π₯ Most common: Browser audio recording β Base64 β Transcription
let mediaRecorder: MediaRecorder;
let audioChunks: Blob[] = [];
// Step 1: Start recording
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
mediaRecorder = new MediaRecorder(stream);
mediaRecorder.ondataavailable = (event) => {
audioChunks.push(event.data);
};
mediaRecorder.start();
// Step 2: Stop recording and transcribe
mediaRecorder.onstop = async () => {
const audioBlob = new Blob(audioChunks, { type: 'audio/wav' });
// SAFE method for large files - use FileReader (recommended)
const base64 = await new Promise<string>((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => {
const dataUrl = reader.result as string;
const base64Data = dataUrl.split(',')[1]; // Extract base64 part
resolve(base64Data);
};
reader.onerror = reject;
reader.readAsDataURL(audioBlob);
});
// Transcribe using base64 (preferred method)
const { text } = await blink.ai.transcribeAudio({
audio: base64, // Raw base64 string
language: 'en'
});
console.log('Transcription:', text);
audioChunks = []; // Reset for next recording
};
// Alternative: Data URL format (also supported)
const reader = new FileReader();
reader.onload = async () => {
const dataUrl = reader.result as string;
const { text } = await blink.ai.transcribeAudio({
audio: dataUrl, // Data URL format
language: 'en'
});
};
reader.readAsDataURL(audioBlob);
// File upload transcription
const fileInput = document.getElementById('audioFile') as HTMLInputElement;
const file = fileInput.files[0];
// Option 1: Convert to base64 using FileReader (recommended for large files)
const base64Audio = await new Promise<string>((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => {
const dataUrl = reader.result as string;
const base64Data = dataUrl.split(',')[1]; // Extract base64 part
resolve(base64Data);
};
reader.onerror = reject;
reader.readAsDataURL(file);
});
const { text } = await blink.ai.transcribeAudio({
audio: base64Audio,
language: 'en'
});
// Option 2: Use ArrayBuffer directly (works for any file size)
const arrayBuffer = await file.arrayBuffer();
const { text } = await blink.ai.transcribeAudio({
audio: arrayBuffer,
language: 'en'
});
// Option 3: Use Uint8Array directly (works for any file size)
const arrayBuffer = await file.arrayBuffer();
const uint8Array = new Uint8Array(arrayBuffer);
const { text } = await blink.ai.transcribeAudio({
audio: uint8Array,
language: 'en'
});
// Public URL transcription (for hosted audio files)
const { text } = await blink.ai.transcribeAudio({
audio: 'https://example.com/audio/meeting.mp3',
language: 'en'
});
// Advanced options
const { text } = await blink.ai.transcribeAudio({
audio: base64Audio,
language: 'en',
model: 'whisper-1',
response_format: 'verbose_json' // Get timestamps and confidence scores
});
// Supported audio formats: MP3, WAV, M4A, FLAC, OGG, WebM
// Supported input types:
// - string: Base64 data, Data URL (data:audio/...;base64,...), or public URL (https://...)
// - ArrayBuffer: Raw audio buffer (works for any file size)
// - Uint8Array: Audio data as byte array (works for any file size)
// - number[]: Audio data as number array
// β οΈ IMPORTANT: For large audio files, use FileReader or ArrayBuffer/Uint8Array
// Avoid btoa(String.fromCharCode(...array)) as it crashes with large files
// Streaming support
await blink.ai.streamText(
{ prompt: 'Write a story...' },
(chunk) => console.log(chunk)
)
// Streaming with web search
await blink.ai.streamText(
{ prompt: 'Latest AI news', search: true },
(chunk) => console.log(chunk)
)
// React streaming example - parse chunks for immediate UI display
const [streamingText, setStreamingText] = useState('')
await blink.ai.streamText(
{ prompt: 'Write a story about AI...' },
(chunk) => {
setStreamingText(prev => prev + chunk) // chunk is a string
}
)
```
### Data Operations
```typescript
// Simple text extraction (default - returns single string)
const text = await blink.data.extractFromUrl('https://example.com/document.pdf');
console.log(typeof text); // 'string'
// Extract with chunking enabled
const chunks = await blink.data.extractFromUrl('https://example.com/document.pdf', {
chunking: true,
chunkSize: 2000
});
console.log(Array.isArray(chunks)); // true
// Extract from different file types
const csvText = await blink.data.extractFromUrl('https://example.com/data.csv');
const htmlText = await blink.data.extractFromUrl('https://example.com/page.html');
const jsonText = await blink.data.extractFromUrl('https://example.com/config.json');
// Extract from uploaded file blob (simple)
const fileInput = document.getElementById('fileInput') as HTMLInputElement;
const file = fileInput.files[0];
const extractedText = await blink.data.extractFromBlob(file);
// Extract from uploaded file blob (with chunking)
const chunks = await blink.data.extractFromBlob(file, {
chunking: true,
chunkSize: 3000
});
// Website scraping (NEW!) - Crystal clear destructuring
const { markdown, metadata, links, extract } = await blink.data.scrape('https://example.com');
console.log(markdown); // Clean markdown content
console.log(metadata.title); // Page title
console.log(links.length); // Number of links found
// Even cleaner - destructure only what you need
const { metadata, extract } = await blink.data.scrape('https://blog.example.com/article');
console.log(metadata.title); // Always available
console.log(extract.headings); // Always an array
// Website screenshots (NEW!)
const screenshotUrl = await blink.data.screenshot('https://example.com');
console.log(screenshotUrl); // Direct URL to screenshot image
// Full-page screenshot with custom dimensions
const fullPageUrl = await blink.data.screenshot('https://example.com', {
fullPage: true,
width: 1920,
height: 1080
});
// π₯ Web Search (NEW!) - Google search results with clean structure
// Perfect for getting real-time information and current data
// Basic web search - just provide a query
const searchResults = await blink.data.search('chatgpt');
console.log(searchResults.organic_results); // Main search results
console.log(searchResults.related_searches); // Related search suggestions
console.log(searchResults.people_also_ask); // People also ask questions
// Search with location for local results
const localResults = await blink.data.search('best restaurants', {
location: 'San Francisco,CA,United States'
});
console.log(localResults.local_results); // Local business results
console.log(localResults.organic_results); // Regular web results
// News search - get latest news articles
const newsResults = await blink.data.search('artificial intelligence', {
type: 'news'
});
console.log(newsResults.news_results); // News articles with dates and sources
// Image search - find images
const imageResults = await blink.data.search('elon musk', {
type: 'images',
limit: 20
});
console.log(imageResults.image_results); // Image results with thumbnails
// Search in different languages
const spanishResults = await blink.data.search('noticias tecnologΓa', {
language: 'es',
type: 'news'
});
// Shopping search - find products
const shoppingResults = await blink.data.search('macbook pro', {
type: 'shopping'
});
console.log(shoppingResults.shopping_results); // Product results with prices
// All search types return consistent, structured data:
// - organic_results: Main search results (always included)
// - related_searches: Related search suggestions
// - people_also_ask: FAQ-style questions and answers
// - local_results: Local businesses (when location provided)
// - news_results: News articles (when type='news')
// - image_results: Images (when type='images')
// - shopping_results: Products (when type='shopping')
// - ads: Sponsored results (when present)
// π₯ Secure API Proxy (NEW!) - Make API calls with secret substitution
// Basic API call with secret substitution
const response = await blink.data.fetch({
url: 'https://api.sendgrid.com/v3/mail/send',
method: 'POST',
headers: {
'Authorization': 'Bearer {{sendgrid_api_key}}', // Secret replaced server-side
'Content-Type': 'application/json'
},
body: {
from: { email: 'me@example.com' },
personalizations: [{ to: [{ email: 'user@example.com' }] }],
subject: 'Hello from Blink',
content: [{ type: 'text/plain', value: 'Sent securely through Blink!' }]
}
});
console.log('Email sent:', response.status === 200);
console.log('Response:', response.body);
console.log('Took:', response.durationMs, 'ms');
// GET request with secret in URL and query params
const weatherData = await blink.data.fetch({
url: 'https://api.openweathermap.org/data/2.5/weather',
method: 'GET',
query: {
q: 'London',
appid: '{{openweather_api_key}}', // Secret replaced in query params
units: 'metric'
}
});
console.log('Weather:', weatherData.body.main.temp, 'Β°C');
// Async/background requests (fire-and-forget)
const asyncResponse = await blink.data.fetchAsync({
url: 'https://api.stripe.com/v1/customers',
method: 'POST',
headers: {
'Authorization': 'Bearer {{stripe_secret_key}}',
'Content-Type': 'application/x-www-form-urlencoded'
},
body: 'email=customer@example.com&name=John Doe'
});
console.log(asyncResponse.status); // 'triggered'
console.log(asyncResponse.message); // 'Request triggered in background'
// Multiple secrets in different places
const complexRequest = await blink.data.fetch({
url: 'https://api.github.com/repos/{{github_username}}/{{repo_name}}/issues',
method: 'POST',
headers: {
'Authorization': 'token {{github_token}}',
'Accept': 'application/vnd.github.v3+json',
'User-Agent': '{{app_name}}'
},
body: {
title: 'Bug Report',
body: 'Found via {{app_name}} monitoring'
}
});
// Secret substitution works everywhere:
// - URL path: /api/{{version}}/users
// - Query params: ?key={{api_key}}&user={{user_id}}
// - Headers: Authorization: Bearer {{token}}
// - Body: { "apiKey": "{{secret}}", "data": "{{value}}" }
// Error handling for data extraction
try {
const result = await blink.data.extractFromUrl('https://example.com/huge-file.pdf');
} catch (error) {
if (error instanceof BlinkDataError) {
console.error('Data processing error:', error.message);
}
}
```
### Storage Operations
```typescript
// Upload files (returns public URL directly)
const { publicUrl } = await blink.storage.upload(
file,
`uploads/${Date.now()}.${file.name.split('.').pop()}`, // β
Extract original extension
{
upsert: true,
onProgress: (percent) => console.log(`${percent}%`)
}
)
// β WRONG - Hardcoded extensions break HEIC/PNG/WebP files
const wrong = await blink.storage.upload(file, `uploads/${Date.now()}.jpg`) // Corrupts non-JPG files
// β
CORRECT - Extract file extension
const correct = await blink.storage.upload(file, `uploads/${Date.now()}.${file.name.split('.').pop()}`)
// Remove files
await blink.storage.remove('file1.jpg', 'file2.jpg')
```
### Notifications Operations
```typescript
// π₯ Email Notifications (NEW!) - Send emails with attachments, custom branding, and delivery tracking
// Send a simple email - returns success status and message ID
const result = await blink.notifications.email({
to: 'customer@example.com',
subject: 'Your order has shipped!',
html: '<h1>Order Confirmation</h1><p>Your order #12345 is on its way.</p>'
})
console.log(result.success) // true/false - whether email was sent
console.log(result.messageId) // "msg_abc123..." - unique message identifier
// Send with plain text fallback (recommended for better deliverability)
const { success, messageId } = await blink.notifications.email({
to: 'customer@example.com',
subject: 'Welcome to our platform!',
html: '<h1>Welcome!</h1><p>Thanks for joining us.</p>',
text: 'Welcome!\n\nThanks for joining us.' // Plain text version
})
// Send an email with attachments and custom branding
const result = await blink.notifications.email({
to: ['team@example.com', 'manager@example.com'],
from: 'invoices@mycompany.com', // Must be valid email address
replyTo: 'support@mycompany.com',
subject: 'New Invoice #12345',
html: `
<div style="font-family: Arial, sans-serif;">
<h2>Invoice Ready</h2>
<p>Please find the invoice attached.</p>
</div>
`,
text: 'Invoice Ready\n\nPlease find the invoice attached.',
cc: 'accounting@mycompany.com',
bcc: 'archive@mycompany.com',
attachments: [
{
url: 'https://mycompany.com/invoices/12345.pdf',
filename: 'Invoice-12345.pdf', // Custom filename
type: 'application/pdf' // MIME type (optional)
},
{
url: 'https://mycompany.com/terms.pdf',
filename: 'Terms-of-Service.pdf'
}
]
})
console.log(`Email ${result.success ? 'sent' : 'failed'}`)
console.log(`Message ID: ${result.messageId}`)
// Send to multiple recipients with different recipient types
const { success, messageId } = await blink.notifications.email({
to: ['customer1@example.com', 'customer2@example.com'],
cc: ['manager@example.com'],
bcc: ['audit@example.com', 'backup@example.com'],
from: 'notifications@mycompany.com',
subject: 'Monthly Newsletter',
html: '<h2>This Month\'s Updates</h2><p>Here are the highlights...</p>'
})
// Dynamic email content with user data
const user = await blink.auth.me()
const welcomeEmail = await blink.notifications.email({
to: user.email,
from: 'welcome@mycompany.com',
subject: `Welcome ${user.displayName}!`,
html: `
<h1>Hi ${user.displayName}!</h1>
<p>Welcome to our platform. Your account is now active.</p>
<p>Account ID: ${user.id}</p>
<a href="https://myapp.com/dashboard">Get Started</a>
`,
text: `Hi ${user.displayName}!\n\nWelcome to our platform. Your account is now active.\nAccount ID: ${user.id}\n\nGet Started: https://myapp.com/dashboard`
})
// Comprehensive error handling with detailed error information
try {
const result = await blink.notifications.email({
to: 'customer@example.com',
subject: 'Important Update',
html: '<p>This is an important update about your account.</p>'
})
if (result.success) {
console.log('β
Email sent successfully!')
console.log('π§ Message ID:', result.messageId)
} else {
console.error('β Email failed to send')
// Handle failed send (retry logic, fallback notification, etc.)
}
} catch (error) {
if (error instanceof BlinkNotificationsError) {
console.error('β Email error:', error.message)
// Common error scenarios:
// - "The 'to', 'subject', and either 'html' or 'text' fields are required."
// - "Invalid email address format"
// - "Attachment URL must be accessible"
// - "Failed to send email: Rate limit exceeded"
// Handle specific error types
if (error.message.includes('Rate limit')) {
// Implement retry with backoff
console.log('β³ Rate limited, will retry later')
} else if (error.message.includes('Invalid email')) {
// Log invalid email for cleanup
console.log('π§ Invalid email address, removing from list')
}
} else {
console.error('β Unexpected error:', error)
}
}
// Email validation and best practices
const validateAndSendEmail = async (recipient: string, subject: string, content: string) => {
// Basic validation
if (!recipient.includes('@') || !subject.trim() || !content.trim()) {
throw new Error('Invalid email parameters')
}
try {
const result = await blink.notifications.email({
to: recipient,
from: 'noreply@mycompany.com',
subject: subject,
html: `
<div style="max-width: 600px; margin: 0 auto; font-family: Arial, sans-serif;">
<div style="background: #f8f9fa; padding: 20px; text-align: center;">
<h1 style="color: #333; margin: 0;">My Company</h1>
</div>
<div style="padding: 20px;">
${content}
</div>
<div style="background: #f8f9fa; padding: 15px; text-align: center; font-size: 12px; color: #666;">
<p>Β© 2024 My Company. All rights reserved.</p>
<p><a href="https://mycompany.com/unsubscribe">Unsubscribe</a></p>
</div>
</div>
`,
text: content.replace(/<[^>]*>/g, '') // Strip HTML for text version
})
return result
} catch (error) {
console.error(`Failed to send email to ${recipient}:`, error)
throw error
}
}
// Usage with validation
try {
const result = await validateAndSendEmail(
'customer@example.com',
'Account Verification Required',
'<p>Please verify your account by clicking the link below.</p><a href="https://myapp.com/verify">Verify Account</a>'
)
console.log('Email sent with ID:', result.messageId)
} catch (error) {
console.error('Email validation or sending failed:', error.message)
}
// Bulk email sending with error handling
const sendBulkEmails = async (recipients: string[], subject: string, htmlContent: string) => {
const results = []
for (const recipient of recipients) {
try {
const result = await blink.notifications.email({
to: recipient,
from: 'newsletter@mycompany.com',
subject,
html: htmlContent,
text: htmlContent.replace(/<[^>]*>/g, '')
})