Skip to main content
Artifacts in Open WebUI provide a built-in key-value storage API for persistent data. This enables AI models to create and maintain long-lived content such as journals, trackers, leaderboards, collaborative tools, and interactive applications that persist across chat sessions.

Overview

Artifacts enable:
  • Persistent key-value data storage
  • Personal and shared data scopes
  • JSON-based structured data
  • Cross-session persistence
  • Collaborative features
  • Interactive AI-powered applications
Artifacts are built directly into Open WebUI, providing a simple storage API that AI models can use to create stateful applications without external databases.

What Are Artifacts?

Artifacts are persistent data objects that survive beyond individual chat conversations. Unlike memories which store context and preferences, artifacts store structured application data. Common use cases:
  • Journals: Daily logs and note-taking
  • Trackers: Habit tracking, goal monitoring
  • Leaderboards: Competitive rankings and scores
  • Dashboards: Data visualization and metrics
  • Collaborative Docs: Shared documents and wikis
  • Games: Persistent game state

Data Scopes

Artifacts support two data scopes:

Personal Scope

Data accessible only to the individual user:
// Store personal data
const personalData = {
  key: "my-journal",
  scope: "personal",
  value: {
    entries: [
      {
        date: "2024-03-02",
        content: "Today I learned about artifacts..."
      }
    ]
  }
};

Shared Scope

Data accessible to all users (workspace-wide):
// Store shared data
const sharedData = {
  key: "team-leaderboard",
  scope: "shared",
  value: {
    scores: [
      { user: "Alice", points: 150 },
      { user: "Bob", points: 120 }
    ]
  }
};

Storage Operations

Store Data

Save or update an artifact:
import requests
import json

url = "http://localhost:8080/api/artifacts/store"
payload = {
    "key": "habit-tracker",
    "scope": "personal",
    "value": {
        "habits": [
            {"name": "Exercise", "completed": ["2024-03-01", "2024-03-02"]},
            {"name": "Reading", "completed": ["2024-03-01"]}
        ]
    }
}

response = requests.post(url, json=payload)
result = response.json()

if result["success"]:
    print("Artifact stored successfully")

Retrieve Data

Get an artifact by key:
import requests

url = "http://localhost:8080/api/artifacts/get"
params = {
    "key": "habit-tracker",
    "scope": "personal"
}

response = requests.get(url, params=params)
artifact = response.json()

if artifact:
    print(f"Habits: {artifact['value']['habits']}")
else:
    print("Artifact not found")

List Artifacts

Get all artifacts for a scope:
import requests

# List personal artifacts
response = requests.get(
    "http://localhost:8080/api/artifacts/list",
    params={"scope": "personal"}
)
personal_artifacts = response.json()

for artifact in personal_artifacts:
    print(f"Key: {artifact['key']}")
    print(f"Updated: {artifact['updated_at']}")

Delete Artifact

Remove an artifact:
import requests

url = "http://localhost:8080/api/artifacts/delete"
params = {
    "key": "old-tracker",
    "scope": "personal"
}

response = requests.delete(url, params=params)

if response.json()["success"]:
    print("Artifact deleted")

Example Applications

Daily Journal

import requests
from datetime import datetime

class JournalApp:
    def __init__(self):
        self.base_url = "http://localhost:8080/api/artifacts"
        self.key = "daily-journal"
    
    def add_entry(self, content):
        """Add a new journal entry."""
        # Get existing journal
        response = requests.get(
            f"{self.base_url}/get",
            params={"key": self.key, "scope": "personal"}
        )
        
        journal = response.json() or {"entries": []}
        
        # Add new entry
        entry = {
            "date": datetime.now().isoformat(),
            "content": content
        }
        journal["entries"].append(entry)
        
        # Save updated journal
        requests.post(
            f"{self.base_url}/store",
            json={
                "key": self.key,
                "scope": "personal",
                "value": journal
            }
        )
    
    def get_recent_entries(self, count=5):
        """Get recent journal entries."""
        response = requests.get(
            f"{self.base_url}/get",
            params={"key": self.key, "scope": "personal"}
        )
        
        journal = response.json()
        if journal:
            entries = journal["entries"][-count:]
            return entries
        return []

# Usage
journal = JournalApp()
journal.add_entry("Today I learned about Open WebUI artifacts!")
recent = journal.get_recent_entries(3)

Habit Tracker

import requests
from datetime import datetime, date

class HabitTracker:
    def __init__(self):
        self.base_url = "http://localhost:8080/api/artifacts"
        self.key = "habit-tracker"
    
    def add_habit(self, habit_name):
        """Create a new habit to track."""
        tracker = self._load_tracker()
        
        if habit_name not in tracker["habits"]:
            tracker["habits"][habit_name] = {
                "created": datetime.now().isoformat(),
                "completed_dates": []
            }
            self._save_tracker(tracker)
    
    def mark_complete(self, habit_name, date_str=None):
        """Mark a habit as completed for today or specific date."""
        if date_str is None:
            date_str = date.today().isoformat()
        
        tracker = self._load_tracker()
        
        if habit_name in tracker["habits"]:
            completed = tracker["habits"][habit_name]["completed_dates"]
            if date_str not in completed:
                completed.append(date_str)
                self._save_tracker(tracker)
    
    def get_streak(self, habit_name):
        """Calculate current streak for a habit."""
        tracker = self._load_tracker()
        
        if habit_name not in tracker["habits"]:
            return 0
        
        completed = sorted(
            tracker["habits"][habit_name]["completed_dates"],
            reverse=True
        )
        
        streak = 0
        current_date = date.today()
        
        for completed_date in completed:
            if completed_date == current_date.isoformat():
                streak += 1
                current_date = current_date - timedelta(days=1)
            else:
                break
        
        return streak
    
    def _load_tracker(self):
        response = requests.get(
            f"{self.base_url}/get",
            params={"key": self.key, "scope": "personal"}
        )
        return response.json() or {"habits": {}}
    
    def _save_tracker(self, tracker):
        requests.post(
            f"{self.base_url}/store",
            json={
                "key": self.key,
                "scope": "personal",
                "value": tracker
            }
        )

# Usage
tracker = HabitTracker()
tracker.add_habit("Exercise")
tracker.mark_complete("Exercise")
print(f"Current streak: {tracker.get_streak('Exercise')} days")

Team Leaderboard

import requests
from typing import List, Dict

class Leaderboard:
    def __init__(self, board_name="default"):
        self.base_url = "http://localhost:8080/api/artifacts"
        self.key = f"leaderboard-{board_name}"
    
    def add_score(self, user_name: str, points: int):
        """Add or update a user's score."""
        board = self._load_board()
        
        # Find existing user
        user_entry = next(
            (u for u in board["scores"] if u["name"] == user_name),
            None
        )
        
        if user_entry:
            user_entry["points"] += points
        else:
            board["scores"].append({
                "name": user_name,
                "points": points
            })
        
        # Sort by points (descending)
        board["scores"].sort(key=lambda x: x["points"], reverse=True)
        
        self._save_board(board)
    
    def get_top(self, count: int = 10) -> List[Dict]:
        """Get top N players."""
        board = self._load_board()
        return board["scores"][:count]
    
    def get_rank(self, user_name: str) -> int:
        """Get a user's rank (1-based)."""
        board = self._load_board()
        
        for i, entry in enumerate(board["scores"], start=1):
            if entry["name"] == user_name:
                return i
        
        return -1  # Not found
    
    def _load_board(self):
        response = requests.get(
            f"{self.base_url}/get",
            params={"key": self.key, "scope": "shared"}
        )
        return response.json() or {"scores": []}
    
    def _save_board(self, board):
        requests.post(
            f"{self.base_url}/store",
            json={
                "key": self.key,
                "scope": "shared",
                "value": board
            }
        )

# Usage
board = Leaderboard("weekly-challenge")
board.add_score("Alice", 50)
board.add_score("Bob", 30)
board.add_score("Alice", 20)  # Alice now has 70 total

top_players = board.get_top(5)
for i, player in enumerate(top_players, start=1):
    print(f"{i}. {player['name']}: {player['points']} points")

Interactive Dashboard

import requests
from datetime import datetime, timedelta

class MetricsDashboard:
    def __init__(self):
        self.base_url = "http://localhost:8080/api/artifacts"
        self.key = "metrics-dashboard"
    
    def log_metric(self, metric_name: str, value: float):
        """Log a metric value with timestamp."""
        dashboard = self._load_dashboard()
        
        if metric_name not in dashboard["metrics"]:
            dashboard["metrics"][metric_name] = []
        
        dashboard["metrics"][metric_name].append({
            "timestamp": datetime.now().isoformat(),
            "value": value
        })
        
        # Keep only last 100 entries per metric
        dashboard["metrics"][metric_name] = \
            dashboard["metrics"][metric_name][-100:]
        
        self._save_dashboard(dashboard)
    
    def get_latest(self, metric_name: str):
        """Get the most recent value for a metric."""
        dashboard = self._load_dashboard()
        
        if metric_name in dashboard["metrics"]:
            entries = dashboard["metrics"][metric_name]
            if entries:
                return entries[-1]["value"]
        
        return None
    
    def get_average(self, metric_name: str, hours: int = 24):
        """Calculate average over the last N hours."""
        dashboard = self._load_dashboard()
        
        if metric_name not in dashboard["metrics"]:
            return None
        
        cutoff = datetime.now() - timedelta(hours=hours)
        entries = dashboard["metrics"][metric_name]
        
        recent = [
            e["value"] for e in entries
            if datetime.fromisoformat(e["timestamp"]) > cutoff
        ]
        
        return sum(recent) / len(recent) if recent else None
    
    def _load_dashboard(self):
        response = requests.get(
            f"{self.base_url}/get",
            params={"key": self.key, "scope": "shared"}
        )
        return response.json() or {"metrics": {}}
    
    def _save_dashboard(self, dashboard):
        requests.post(
            f"{self.base_url}/store",
            json={
                "key": self.key,
                "scope": "shared",
                "value": dashboard
            }
        )

# Usage
dashboard = MetricsDashboard()
dashboard.log_metric("cpu_usage", 45.2)
dashboard.log_metric("memory_usage", 62.8)

print(f"Current CPU: {dashboard.get_latest('cpu_usage')}%")
print(f"Avg CPU (24h): {dashboard.get_average('cpu_usage', 24):.1f}%")

Best Practices

Data Structure

  1. Use JSON: Store well-structured JSON data
  2. Version Your Schema: Include version fields for migrations
  3. Keep It Small: Store only necessary data
  4. Index When Needed: Use keys that facilitate lookups
# Good: Structured, versioned data
artifact = {
    "key": "user-settings",
    "value": {
        "version": "1.0",
        "preferences": {
            "theme": "dark",
            "language": "en"
        },
        "updated_at": "2024-03-02T10:00:00Z"
    }
}

Performance

  1. Cache Locally: Don’t fetch on every operation
  2. Batch Updates: Combine multiple changes
  3. Limit Size: Keep artifacts under 1MB
  4. Clean Old Data: Implement data retention policies

Security

  1. Validate Input: Always validate before storing
  2. Sanitize Output: Clean data before display
  3. Scope Appropriately: Use personal scope for sensitive data
  4. Don’t Store Secrets: Never store passwords or API keys

Error Handling

import requests

def safe_artifact_operation():
    try:
        response = requests.post(
            "http://localhost:8080/api/artifacts/store",
            json={
                "key": "my-data",
                "scope": "personal",
                "value": {"data": "value"}
            },
            timeout=5
        )
        response.raise_for_status()
        return response.json()
    
    except requests.exceptions.Timeout:
        print("Request timed out")
    except requests.exceptions.HTTPError as e:
        print(f"HTTP error: {e}")
    except Exception as e:
        print(f"Error: {e}")
    
    return None

Troubleshooting

Artifact Not Found

  • Verify the key is correct
  • Check you’re using the right scope (personal vs shared)
  • Ensure the artifact was successfully saved
  • Confirm you have permission to access the scope

Data Not Persisting

  • Check the API response for errors
  • Verify JSON data is valid
  • Ensure sufficient storage space
  • Review application logs

Performance Issues

  • Reduce artifact size
  • Implement caching
  • Limit update frequency
  • Archive old data

Next Steps

  • Learn about Memories for context storage
  • Explore Functions to create artifact-powered tools
  • Build Skills that leverage artifacts

Build docs developers (and LLMs) love