Workspaces
Workspaces are app-specific organizational units for grouping related data within a repository. They separate different types of content — like "content", "dam", "customers" — while sharing the same version control and branching infrastructure. Workspaces are completely independent of multi-tenancy (which is handled by RaisinDB's built-in tenant isolation).
What is a Workspace?
In the Admin Console, workspaces are displayed as cards showing their purpose and available NodeTypes:

A workspace is a named container for nodes:
-- Each workspace is queryable as a table
SELECT * FROM default; -- User content
SELECT * FROM raisin:system; -- System metadata
SELECT * FROM raisin:access_control;-- Users, roles, permissions
SELECT * FROM functions; -- Serverless functions
Workspaces provide:
- Isolation: Content in one workspace doesn't interfere with another
- Organization: Separate concerns (content, config, users, etc.)
- Security: Different access controls per workspace
- Performance: Independent indexes and optimization
System Workspaces
RaisinDB includes built-in workspaces:
default
Primary content workspace for your application:
-- User-facing content
SELECT * FROM default WHERE node_type = 'blog:Article';
-- This is where most INSERT/UPDATE operations happen
INSERT INTO default (path, node_type, properties) VALUES (
'/content/blog/my-post',
'blog:Article',
'{"title": "My Article"}'
);
raisin:system
System configuration and schemas. The workspace name is the table, and you filter by node_type:
-- Query the system workspace for NodeType definitions
SELECT path, properties->>'name' AS name
FROM "raisin:system"
WHERE node_type = 'raisin:NodeType';
-- Query archetypes
SELECT path, properties->>'name' AS name
FROM "raisin:system"
WHERE node_type = 'raisin:Archetype';
-- Query element definitions
SELECT path, properties->>'name' AS name
FROM "raisin:system"
WHERE node_type = 'raisin:ElementType';
raisin:access_control
User management and permissions. Users, groups, and roles are nodes in this workspace:
-- Query users (nodes at /users/...)
SELECT path, properties->>'user_id' AS user_id,
properties->>'email' AS email
FROM "raisin:access_control"
WHERE node_type = 'raisin:User';
-- Query groups (nodes at /groups/...)
SELECT path, properties->>'group_id' AS group_id
FROM "raisin:access_control"
WHERE node_type = 'raisin:Group';
-- Query roles (nodes at /roles/...)
SELECT path, properties->>'role_id' AS role_id
FROM "raisin:access_control"
WHERE node_type = 'raisin:Role';
functions
Serverless JavaScript functions:
-- Stored functions
SELECT * FROM functions WHERE path = '/api/custom-endpoint';
-- Triggers
SELECT * FROM functions WHERE type = 'trigger';
-- Create a function
INSERT INTO functions (path, code, trigger_on) VALUES (
'/api/hello',
'export default function(req) { return { message: "Hello" }; }',
NULL
);
packages
Installed RAP (RaisinDB Application Package) content:
-- Installed packages
SELECT * FROM packages;
-- Package content
SELECT * FROM packages WHERE path LIKE '/packages/cms-toolkit/%';
Creating Custom Workspaces
Create workspaces for specific purposes:
-- Create a workspace for drafts
CREATE WORKSPACE drafts;
-- Create a workspace for archives
CREATE WORKSPACE archives;
-- Create a workspace for media
CREATE WORKSPACE media;
-- Create a workspace for analytics
CREATE WORKSPACE analytics;
Workspace Metadata
-- List all workspaces
SELECT name, created_at, created_by, description
FROM __workspaces__
ORDER BY name;
-- Get workspace details
SELECT * FROM __workspaces__
WHERE name = 'drafts';
Using Workspaces
Insert into Workspace
-- Insert into default workspace
INSERT INTO default (path, node_type, properties) VALUES (
'/content/blog/post1',
'blog:Article',
'{"title": "Published Article"}'
);
-- Insert into drafts workspace
INSERT INTO drafts (path, node_type, properties) VALUES (
'/content/blog/draft1',
'blog:Article',
'{"title": "Draft Article"}'
);
-- Insert into media workspace
INSERT INTO media (path, node_type, properties) VALUES (
'/images/header.jpg',
'media:Image',
'{"url": "https://cdn.example.com/header.jpg"}'
);
Query from Workspace
-- Query default workspace
SELECT * FROM default WHERE node_type = 'blog:Article';
-- Query drafts workspace
SELECT * FROM drafts WHERE properties->>'status' = 'review';
-- Query media workspace
SELECT * FROM media WHERE node_type = 'media:Image';
-- Join across workspaces
SELECT
d.properties->>'title' AS title,
m.properties->>'url' AS image_url
FROM default d
LEFT JOIN media m ON d.properties->>'featuredImage' = m.path
WHERE d.node_type = 'blog:Article';
Cross-Workspace References
-- Article in default references media in media workspace
INSERT INTO default (path, node_type, properties) VALUES (
'/content/blog/post1',
'blog:Article',
'{
"title": "My Article",
"featuredImage": "/images/header.jpg"
}'
);
-- Resolve reference across workspaces
SELECT
d.properties->>'title' AS title,
(SELECT properties FROM media WHERE path = d.properties->>'featuredImage') AS image
FROM default d
WHERE path = '/content/blog/post1';
Workspace Patterns
Content Staging
Separate draft and published content:
-- Create staging workspaces
CREATE WORKSPACE drafts;
CREATE WORKSPACE review;
CREATE WORKSPACE published;
-- Author writes in drafts
INSERT INTO drafts (path, node_type, properties) VALUES (
'/content/blog/new-article',
'blog:Article',
'{"title": "New Article", "status": "draft"}'
);
-- Move to review workspace
INSERT INTO review (path, node_type, properties)
SELECT path, node_type, properties
FROM drafts
WHERE path = '/content/blog/new-article';
DELETE FROM drafts WHERE path = '/content/blog/new-article';
-- Publish to published workspace
INSERT INTO published (path, node_type, properties)
SELECT path, node_type, properties || '{"published": true, "publishedAt": NOW()}'
FROM review
WHERE path = '/content/blog/new-article';
Multi-Tenancy
Isolate data per tenant:
-- Create workspace per tenant
CREATE WORKSPACE tenant:acme;
CREATE WORKSPACE tenant:globex;
CREATE WORKSPACE tenant:initech;
-- Tenant-specific data
INSERT INTO tenant:acme (path, node_type, properties) VALUES (
'/content/page',
'cms:Page',
'{"title": "Acme Corp Homepage"}'
);
INSERT INTO tenant:globex (path, node_type, properties) VALUES (
'/content/page',
'cms:Page',
'{"title": "Globex Homepage"}'
);
-- Query tenant data
SELECT * FROM tenant:acme WHERE node_type = 'cms:Page';
Environment Separation
Different workspaces for different environments:
-- Create environment workspaces
CREATE WORKSPACE env:development;
CREATE WORKSPACE env:staging;
CREATE WORKSPACE env:production;
-- Development config
INSERT INTO env:development (path, node_type, properties) VALUES (
'/config/api',
'config:API',
'{"endpoint": "https://dev-api.example.com"}'
);
-- Production config
INSERT INTO env:production (path, node_type, properties) VALUES (
'/config/api',
'config:API',
'{"endpoint": "https://api.example.com"}'
);
-- Application reads from appropriate workspace
SELECT properties->>'endpoint'
FROM env:production
WHERE path = '/config/api';
Archive Storage
Move old content to archive workspace:
-- Create archive workspace
CREATE WORKSPACE archives;
-- Move old content to archive
INSERT INTO archives (path, node_type, properties)
SELECT path, node_type, properties
FROM default
WHERE (properties->>'publishedAt')::timestamp < NOW() - INTERVAL '1 year';
-- Remove from default
DELETE FROM default
WHERE (properties->>'publishedAt')::timestamp < NOW() - INTERVAL '1 year';
-- Query archived content
SELECT * FROM archives WHERE node_type = 'blog:Article';
Workspace Versioning
Workspaces share the same versioning and branching:
-- Create branch (affects all workspaces)
CREATE BRANCH feature/redesign FROM main;
-- Switch branch
SET BRANCH = 'feature/redesign';
-- Changes in any workspace are on this branch
INSERT INTO default (path, node_type, properties) VALUES ('/content/page', 'cms:Page', '{}');
INSERT INTO media (path, node_type, properties) VALUES ('/images/new.jpg', 'media:Image', '{}');
-- Both changes are on feature/redesign branch
SELECT __branch FROM default WHERE path = '/content/page'; -- feature/redesign
SELECT __branch FROM media WHERE path = '/images/new.jpg'; -- feature/redesign
-- Merge brings changes from all workspaces
MERGE BRANCH feature/redesign INTO main;
Workspace Access Control
Control access per workspace:
-- Grant access to specific workspace
GRANT SELECT ON default TO ROLE viewer;
GRANT INSERT, UPDATE, DELETE ON default TO ROLE editor;
-- Restrict access to system workspaces
GRANT SELECT ON raisin:system TO ROLE admin;
REVOKE ALL ON raisin:system FROM ROLE viewer;
-- Tenant isolation
GRANT ALL ON tenant:acme TO ROLE acme_admin;
GRANT SELECT ON tenant:acme TO ROLE acme_viewer;
REVOKE ALL ON tenant:globex FROM ROLE acme_admin; -- No cross-tenant access
Workspace Configuration
Configure workspace behavior:
-- Enable full-text search for workspace
ALTER WORKSPACE default SET INDEXING = 'enabled';
-- Set retention policy
ALTER WORKSPACE archives SET REVISION_RETENTION = 'DURATION 180 DAYS';
-- Configure workspace metadata
ALTER WORKSPACE drafts SET DESCRIPTION = 'Draft content pending review';
-- Set workspace-specific schema
ALTER WORKSPACE media SET ALLOWED_NODE_TYPES = '["media:Image", "media:Video", "media:Document"]';
Workspace Queries
List All Nodes Across Workspaces
-- Union across workspaces
SELECT 'default' AS workspace, path, node_type FROM default
UNION ALL
SELECT 'drafts', path, node_type FROM drafts
UNION ALL
SELECT 'media', path, node_type FROM media;
Count Nodes Per Workspace
-- Using information schema
SELECT workspace_name, COUNT(*) AS node_count
FROM (
SELECT 'default' AS workspace_name, path FROM default
UNION ALL
SELECT 'drafts', path FROM drafts
UNION ALL
SELECT 'media', path FROM media
) combined
GROUP BY workspace_name;
Cross-Workspace Search
-- Search across multiple workspaces
SELECT 'default' AS workspace, path, properties->>'title' AS title
FROM default
WHERE properties->>'title' ILIKE '%search term%'
UNION ALL
SELECT 'drafts', path, properties->>'title'
FROM drafts
WHERE properties->>'title' ILIKE '%search term%';
Deleting Workspaces
-- Delete empty workspace
DROP WORKSPACE temp;
-- Force delete (removes all content)
DROP WORKSPACE old_drafts CASCADE;
Performance Considerations
Workspace Indexes
Each workspace has independent indexes:
-- Create index on default workspace
CREATE INDEX idx_default_type ON default (node_type);
-- Create index on media workspace
CREATE INDEX idx_media_url ON media ((properties->>'url'));
-- Indexes don't affect other workspaces
Workspace Size
-- Get workspace sizes
SELECT
'default' AS workspace,
COUNT(*) AS node_count,
pg_size_pretty(pg_total_relation_size('default')) AS size
FROM default
UNION ALL
SELECT
'media',
COUNT(*),
pg_size_pretty(pg_total_relation_size('media'))
FROM media;
Best Practices
- Use default for primary content: Keep main user-facing content in default
- Separate concerns: Use workspaces to organize different data types (content, dam, customers)
- Don't over-segment: Too many workspaces add complexity
- Document workspace purpose: Add descriptions to workspaces
- Control access: Use workspace-level permissions
- Index independently: Optimize each workspace separately
- Consider query patterns: Design workspaces around how you query
- Use tenants for isolation: Multi-tenancy is handled by RaisinDB's tenant system, not workspaces
Real-World Examples
CMS with Workflow
-- Content lifecycle workspaces
CREATE WORKSPACE content:drafts;
CREATE WORKSPACE content:review;
CREATE WORKSPACE content:published;
CREATE WORKSPACE content:archived;
-- Media library
CREATE WORKSPACE media:images;
CREATE WORKSPACE media:videos;
CREATE WORKSPACE media:documents;
-- Configuration
CREATE WORKSPACE config:site;
CREATE WORKSPACE config:features;
Multi-Site Platform
-- Site-specific workspaces
CREATE WORKSPACE site:corporate;
CREATE WORKSPACE site:blog;
CREATE WORKSPACE site:ecommerce;
-- Shared media
CREATE WORKSPACE shared:media;
-- Each site queries its workspace
SELECT * FROM site:corporate WHERE node_type = 'cms:Page';
SELECT * FROM site:blog WHERE node_type = 'blog:Article';
SaaS Application
-- Tenant isolation
CREATE WORKSPACE customer:123;
CREATE WORKSPACE customer:456;
CREATE WORKSPACE customer:789;
-- Application queries current customer's workspace
SELECT * FROM customer:${customer_id} WHERE user_id = ${user_id};
Next Steps
- Nodes - Create content in workspaces
- Access Control - Secure workspaces with permissions
- Branching - Version control across workspaces
- SQL Reference - Workspace management commands