Skip to content
Back to skills

Source 92007e9052f1b80846642fec

ASecurity

Appwrite Ruby SDK skill. Use when building server-side Ruby applications with Appwrite, including Rails and Sinatra integrations. Covers user management, database/table CRUD, file storage, and functions via API keys.

  • 2 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added November 7, 2026
businessrubybashrailsdebugginggitapidatabasesecurity

Works with

  • cursor
  • cli
  • api

Security analysis

A100/100

Pro scans all 3 files and shows the line behind each finding

Scanned November 7, 2026

npx -y skills add gutsofpratik/OpenClaude-Skills --skill source-92007e9052f1b80846642fec --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Source 92007e9052f1b80846642fec?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Source 92007e9052f1b80846642fec
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/gutsofpratik-source-92007e9052f1b80846642fec/badge)](https://www.skillsdirectory.com/skills/gutsofpratik-source-92007e9052f1b80846642fec)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: ruby
description: Appwrite Ruby SDK skill. Use when building server-side Ruby applications with Appwrite, including Rails and Sinatra integrations. Covers user management, database/table CRUD, file storage, and functions via API keys.
---


# Appwrite Ruby SDK

## Installation

```bash
gem install appwrite
```

## Setting Up the Client

```ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project(ENV['APPWRITE_PROJECT_ID'])
    .set_key(ENV['APPWRITE_API_KEY'])
```

## Code Examples

### User Management

```ruby
users = Users.new(client)

# Create user
user = users.create(user_id: ID.unique, email: 'user@example.com', password: 'password123', name: 'User Name')

# List users
list = users.list(queries: [Query.limit(25)])

# Get user
fetched = users.get(user_id: '[USER_ID]')

# Delete user
users.delete(user_id: '[USER_ID]')
```

### Database Operations

> **Note:** Use `TablesDB` (not the deprecated `Databases` class) for all new code. Only use `Databases` if the existing codebase already relies on it or the user explicitly requests it.
>
> **Tip:** Prefer keyword arguments (e.g., `database_id: '...'`) for all SDK method calls. Only use positional arguments if the existing codebase already uses them or the user explicitly requests it.

```ruby
tables_db = TablesDB.new(client)

# Create database
db = tables_db.create(database_id: ID.unique, name: 'My Database')

# Create row
doc = tables_db.create_row(
    database_id: '[DATABASE_ID]',
    table_id: '[TABLE_ID]',
    row_id: ID.unique,
    data: { title: 'Hello World' }
)

# Query rows
results = tables_db.list_rows(
    database_id: '[DATABASE_ID]',
    table_id: '[TABLE_ID]',
    queries: [Query.equal('title', 'Hello World'), Query.limit(10)]
)

# Get row
row = tables_db.get_row(database_id: '[DATABASE_ID]', table_id: '[TABLE_ID]', row_id: '[ROW_ID]')

# Update row
tables_db.update_row(
    database_id: '[DATABASE_ID]',
    table_id: '[TABLE_ID]',
    row_id: '[ROW_ID]',
    data: { title: 'Updated' }
)

# Delete row
tables_db.delete_row(database_id: '[DATABASE_ID]', table_id: '[TABLE_ID]', row_id: '[ROW_ID]')
```

#### String Column Types

> **Note:** The legacy `string` type is deprecated. Use explicit column types for all new columns.

| Type | Max characters | Indexing | Storage |
|------|---------------|----------|---------|
| `varchar` | 16,383 | Full index (if size ≤ 768) | Inline in row |
| `text` | 16,383 | Prefix only | Off-page |
| `mediumtext` | 4,194,303 | Prefix only | Off-page |
| `longtext` | 1,073,741,823 | Prefix only | Off-page |

- `varchar` is stored inline and counts towards the 64 KB row size limit. Prefer for short, indexed fields like names, slugs, or identifiers.
- `text`, `mediumtext`, and `longtext` are stored off-page (only a 20-byte pointer lives in the row), so they don't consume the row size budget. `size` is not required for these types.

```ruby
# Create table with explicit string column types
tables_db.create_table(
    database_id: '[DATABASE_ID]',
    table_id: ID.unique,
    name: 'articles',
    columns: [
        { key: 'title',    type: 'varchar',    size: 255, required: true  },  # inline, fully indexable
        { key: 'summary',  type: 'text',                  required: false },  # off-page, prefix index only
        { key: 'body',     type: 'mediumtext',            required: false },  # up to ~4 M chars
        { key: 'raw_data', type: 'longtext',              required: false },  # up to ~1 B chars
    ]
)
```

### Query Methods

```ruby
# Filtering
Query.equal('field', 'value')             # == (or pass array for IN)
Query.not_equal('field', 'value')         # !=
Query.less_than('field', 100)             # <
Query.less_than_equal('field', 100)       # <=
Query.greater_than('field', 100)          # >
Query.greater_than_equal('field', 100)    # >=
Query.between('field', 1, 100)            # 1 <= field <= 100
Query.is_null('field')                    # is null
Query.is_not_null('field')                # is not null
Query.starts_with('field', 'prefix')      # starts with
Query.ends_with('field', 'suffix')        # ends with
Query.contains('field', 'sub')            # contains
Query.search('field', 'keywords')         # full-text search (requires index)

# Sorting
Query.order_asc('field')
Query.order_desc('field')

# Pagination
Query.limit(25)                           # max rows (default 25, max 100)
Query.offset(0)                           # skip N rows
Query.cursor_after('[ROW_ID]')            # cursor pagination (preferred)
Query.cursor_before('[ROW_ID]')

# Selection & Logic
Query.select(['field1', 'field2'])        # return only specified fields
Query.or([Query.equal('a', 1), Query.equal('b', 2)])   # OR
Query.and([Query.greater_than('age', 18), Query.less_than('age', 65)])  # AND (default)
```

### File Storage

```ruby
storage = Storage.new(client)

# Upload file
file = storage.create_file(bucket_id: '[BUCKET_ID]', file_id: ID.unique, file: InputFile.from_path('/path/to/file.png'))

# List files
files = storage.list_files(bucket_id: '[BUCKET_ID]')

# Delete file
storage.delete_file(bucket_id: '[BUCKET_ID]', file_id: '[FILE_ID]')
```

#### InputFile Factory Methods

```ruby
InputFile.from_path('/path/to/file.png')             # from filesystem path
InputFile.from_string('Hello world', 'hello.txt')    # from string content
```

### Teams

```ruby
teams = Teams.new(client)

# Create team
team = teams.create(team_id: ID.unique, name: 'Engineering')

# List teams
list = teams.list

# Create membership (invite user by email)
membership = teams.create_membership(
    team_id: '[TEAM_ID]',
    roles: ['editor'],
    email: 'user@example.com'
)

# List memberships
members = teams.list_memberships(team_id: '[TEAM_ID]')

# Update membership roles
teams.update_membership(team_id: '[TEAM_ID]', membership_id: '[MEMBERSHIP_ID]', roles: ['admin'])

# Delete team
teams.delete(team_id: '[TEAM_ID]')
```

> **Role-based access:** Use `Role.team('[TEAM_ID]')` for all team members or `Role.team('[TEAM_ID]', 'editor')` for a specific team role when setting permissions.

### Serverless Functions

```ruby
functions = Functions.new(client)

# Execute function
execution = functions.create_execution(function_id: '[FUNCTION_ID]', body: '{"key": "value"}')

# List executions
executions = functions.list_executions(function_id: '[FUNCTION_ID]')
```

#### Writing a Function Handler (Ruby runtime)

```ruby
# src/main.rb — Appwrite Function entry point
def main(context)
    # context.req.body         — raw body (String)
    # context.req.body_json    — parsed JSON (Hash or nil)
    # context.req.headers      — headers (Hash)
    # context.req.method       — HTTP method
    # context.req.path         — URL path
    # context.req.query        — query params (Hash)

    context.log("Processing: #{context.req.method} #{context.req.path}")

    if context.req.method == 'GET'
        return context.res.json({ message: 'Hello from Appwrite Function!' })
    end

    context.res.json({ success: true })          # JSON
    # context.res.text('Hello')                  # plain text
    # context.res.empty                          # 204
    # context.res.redirect('https://...')         # 302
end
```

### Server-Side Rendering (SSR) Authentication

SSR apps using Ruby frameworks (Rails, Sinatra, etc.) use the **server SDK** to handle auth. You need two clients:

- **Admin client** — uses an API key, creates sessions, bypasses rate limits (reusable singleton)
- **Session client** — uses a session cookie, acts on behalf of a user (create per-request, never share)

```ruby
require 'appwrite'
include Appwrite

# Admin client (reusable)
admin_client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('[PROJECT_ID]')
    .set_key(ENV['APPWRITE_API_KEY'])

# Session client (create per-request)
session_client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('[PROJECT_ID]')

session = cookies['a_session_[PROJECT_ID]']
session_client.set_session(session) if session
```

#### Email/Password Login (Sinatra)

```ruby
post '/login' do
    account = Account.new(admin_client)
    session = account.create_email_password_session(
        email: params[:email],
        password: params[:password]
    )

    # Cookie name must be a_session_<PROJECT_ID>
    response.set_cookie('a_session_[PROJECT_ID]', {
        value: session.secret,
        httponly: true,
        secure: true,
        same_site: :strict,
        path: '/',
    })

    content_type :json
    { success: true }.to_json
end
```

#### Authenticated Requests

```ruby
get '/user' do
    session = request.cookies['a_session_[PROJECT_ID]']
    halt 401, { error: 'Unauthorized' }.to_json unless session

    session_client = Client.new
        .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
        .set_project('[PROJECT_ID]')
        .set_session(session)

    account = Account.new(session_client)
    user = account.get

    content_type :json
    user.to_json
end
```

#### OAuth2 SSR Flow

```ruby
# Step 1: Redirect to OAuth provider
get '/oauth' do
    account = Account.new(admin_client)
    redirect_url = account.create_o_auth2_token(
        provider: OAuthProvider::GITHUB,
        success: 'https://example.com/oauth/success',
        failure: 'https://example.com/oauth/failure'
    )
    redirect redirect_url
end

# Step 2: Handle callback — exchange token for session
get '/oauth/success' do
    account = Account.new(admin_client)
    session = account.create_session(
        user_id: params[:userId],
        secret: params[:secret]
    )

    response.set_cookie('a_session_[PROJECT_ID]', {
        value: session.secret,
        httponly: true, secure: true, same_site: :strict, path: '/',
    })

    content_type :json
    { success: true }.to_json
end
```

> **Cookie security:** Always use `httponly`, `secure`, and `same_site: :strict` to prevent XSS. The cookie name must be `a_session_<PROJECT_ID>`.

> **Forwarding user agent:** Call `session_client.set_forwarded_user_agent(request.user_agent)` to record the end-user's browser info for debugging and security.

## Error Handling

```ruby
require 'appwrite'
include Appwrite

begin
    row = tables_db.get_row(database_id: '[DATABASE_ID]', table_id: '[TABLE_ID]', row_id: '[ROW_ID]')
rescue Appwrite::Exception => e
    puts e.message    # human-readable message
    puts e.code       # HTTP status code (Integer)
    puts e.type       # error type (e.g. 'document_not_found')
    puts e.response   # full response body (Hash)
end
```

**Common error codes:**

| Code | Meaning |
|------|---------|
| `401` | Unauthorized — missing or invalid session/API key |
| `403` | Forbidden — insufficient permissions |
| `404` | Not found — resource does not exist |
| `409` | Conflict — duplicate ID or unique constraint |
| `429` | Rate limited — too many requests |

## Permissions & Roles (Critical)

Appwrite uses permission strings to control access to resources. Each permission pairs an action (`read`, `update`, `delete`, `create`, or `write` which grants create + update + delete) with a role target. By default, **no user has access** unless permissions are explicitly set at the row/file level or inherited from the table/bucket settings. Permissions are arrays of strings built with the `Permission` and `Role` helpers.

```ruby
# Permission and Role are included in the main require
require 'appwrite'
include Appwrite
```

### Database Row with Permissions

```ruby
doc = tables_db.create_row(
    database_id: '[DATABASE_ID]',
    table_id: '[TABLE_ID]',
    row_id: ID.unique,
    data: { title: 'Hello World' },
    permissions: [
        Permission.read(Role.user('[USER_ID]')),     # specific user can read
        Permission.update(Role.user('[USER_ID]')),   # specific user can update
        Permission.read(Role.team('[TEAM_ID]')),     # all team members can read
        Permission.read(Role.any),                   # anyone (including guests) can read
    ]
)
```

### File Upload with Permissions

```ruby
file = storage.create_file(
    bucket_id: '[BUCKET_ID]',
    file_id: ID.unique,
    file: InputFile.from_path('/path/to/file.png'),
    permissions: [
        Permission.read(Role.any),
        Permission.update(Role.user('[USER_ID]')),
        Permission.delete(Role.user('[USER_ID]')),
    ]
)
```

> **When to set permissions:** Set row/file-level permissions when you need per-resource access control. If all rows in a table share the same rules, configure permissions at the table/bucket level and leave row permissions empty.

> **Common mistakes:**
> - **Forgetting permissions** — the resource becomes inaccessible to all users (including the creator)
> - **`Role.any` with `write`/`update`/`delete`** — allows any user, including unauthenticated guests, to modify or remove the resource
> - **`Permission.read(Role.any)` on sensitive data** — makes the resource publicly readable

Files in this skill

  • PROVENANCE.json192 B
  • SKILL.md12.8 KB
  • UPSTREAM-LICENSE.txt1.5 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.