# NocoDB Image Converter
A standalone Python utility that bridges the gap between external image URLs and NocoDB's native attachment system. This tool enables bulk migration of image references stored as text URLs into proper NocoDB attachment fields.
Problem Statement
NocoDB allows two ways to store images:
- Attachment fields - Native file storage with thumbnails, metadata
- URL text fields - External links (brittle, no offline access)
Many NocoDB bases were created with URL text columns. This utility converts them:
code
Before: Product table with Image_URL (text) → "https://example.com/product.jpg"
After: Product table with Product_Images (attachment) → [Inline attachment]How It Works
Core Class
python
class NocoDBImageConverter:
def __init__(self, base_url: str, api_token: str, table_id: str):
self.base_url = base_url
self.api_token = api_token
self.table_id = table_id
self.headers = {"xc-token": api_token, "Content-Type": "application/json"}Key Methods
Fetch Records
python
def get_all_records(self, limit: int = 1000) -> List[Dict]:
"""Paginated fetch of all table records"""
url = f"{self.base_url}/api/v2/tables/{self.table_id}/records"
all_records = []
offset = 0
while True:
response = requests.get(url, headers=self.headers, params={
'limit': limit, 'offset': offset
})
records = response.json().get('list', [])
if not records:
break
all_records.extend(records)
offset += limit
time.sleep(0.5) # Rate limiting
return all_recordsDownload Image
python
def download_image(self, image_url: str, filename: str) -> Optional[bytes]:
"""Download image and validate content-type"""
response = requests.get(image_url, timeout=30)
response.raise_for_status()
content_type = response.headers.get('content-type', '')
if not content_type.startswith('image/'):
return None # Not a valid image
return response.contentUpload as Attachment
python
def upload_attachment(self, file_content: bytes, filename: str) -> Optional[Dict]:
"""Upload to NocoDB storage endpoint"""
files = {'file': (filename, file_content, 'image/*')}
upload_headers = {"xc-token": self.api_token} # No Content-Type for multipart
url = f"{self.base_url}/api/v2/storage/upload"
response = requests.post(url, headers=upload_headers, files=files)
return response.json() # {url, title, mimetype, size}Update Record
python
def update_record_with_attachment(self, record_id: str, attachment_data: List[Dict], attachment_column: str):
"""PATCH record with attachment JSON"""
url = f"{self.base_url}/api/v2/tables/{self.table_id}/records"
data = [{
"Id": record_id,
attachment_column: attachment_data # NocoDB attachment format
}]
response = requests.patch(url, headers=self.headers, json=data)
response.raise_for_status()
return TrueUsage
python
# Configuration
BASE_URL = "https://db.aatmanova.in"
API_TOKEN = "your_nocodb_api_token"
TABLE_ID = "table_id_from_url"
# Initialize
converter = NocoDBImageConverter(BASE_URL, API_TOKEN, TABLE_ID)
# Run conversion
converter.process_records(
image_url_column="Image_URL", # Source: text column with URLs
attachment_column="Product_Images", # Target: attachment field
skip_existing=True # Skip records that already have attachments
)Configuration
| Parameter | Source | Description |
|---|---|---|
base_url | NocoDB instance | e.g., https://db.aatmanova.in |
api_token | NocoDB profile | Generate from Account settings |
table_id | Table URL | Extract from /nc/{table_id}/... |
image_url_column | Column name | Existing text column with URLs |
attachment_column | Column name | Target attachment column |
Output Summary
code
==================================================
CONVERSION SUMMARY
==================================================
Total records processed: 150
✅ Successful updates: 142
⏩ Skipped records: 5
❌ Failed updates: 3
==================================================Safety Features
- skip_existing=True - Won't re-process records that already have attachments
- Content-Type validation - Skips non-image URLs gracefully
- Rate limiting - 0.5s between fetches, 1s between updates
- Error isolation - Single record failure doesn't stop batch
Error Handling
| Error | Cause | Resolution |
|---|---|---|
404 on download | Invalid URL | Marked as failed, continues |
401 on upload | Invalid API token | Check token permissions |
500 on update | Field type mismatch | Verify attachment column exists |
Use Cases
- E-commerce catalogs: Migrate product image URLs to native attachments
- Asset libraries: Consolidate external CDN links to NocoDB storage
- Data migration: Move from Airtable/Sheet URLs to NocoDB native
Limitations
- Requires NocoDB API token with table write permissions
- No directory/file structure support (flat uploads only)
- No automatic thumbnail regeneration (NocoDB handles this)
- Max file size depends on NocoDB server configuration
Architecture Feedback
Spotted a potential optimization or antipattern? Let me know.