Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.refactkit.com/llms.txt

Use this file to discover all available pages before exploring further.

RefactKit is built around the idea that every piece of data belongs to an organization. A single user account can be a member of multiple organizations, and switching between them gives you a completely separate workspace — separate members, separate data, separate settings. This is multi-tenancy: one application, many isolated tenants.

What an organization is

An organization (also called a workspace) is the top-level container for your product’s data. When someone signs up for your app and completes onboarding, they create their first organization. From that point on, everything they do — uploading files, inviting teammates, creating records — happens inside that organization’s context. Organizations have three identifying attributes:
  • Name — a human-readable label shown in the UI (e.g., “Acme Corp”)
  • Slug — a URL-safe identifier derived from the name (e.g., acme-corp) that appears in every workspace URL
  • Logo — an optional image shown in the sidebar and org switcher
Each organization stores its own logo_url and slug.

How data isolation works

Every table that holds tenant-specific data includes an organizationId foreign key that references the organization table. For example, the built-in gallery_image table looks like this:
export const galleryImage = pgTable('gallery_image', {
  id: text('id').primaryKey(),
  name: text('name').notNull(),
  url: text('url').notNull(),
  size: text('size').default('0').notNull(),
  organizationId: text('organization_id')
    .notNull()
    .references(() => organization.id, { onDelete: 'cascade' }),
  createdAt: timestamp('created_at').defaultNow().notNull(),
})
When you add your own tables, always include organizationId so queries filter to the correct tenant automatically.
Always include organizationId in any new table you create. Without it, a query can accidentally return records from other organizations.
The session record also tracks which organization is currently active for a given browser session:
activeOrganizationId: text('active_organization_id'),
This means the server always knows which tenant context to enforce, even before your route logic runs.

The URL slug pattern

Every workspace route lives under /organizations/$slug/. The $slug segment matches the organization’s unique slug, making URLs human-readable and bookmarkable.

Dashboard

/organizations/acme-corp/dashboard

Members

/organizations/acme-corp/members

Gallery

/organizations/acme-corp/gallery

Settings

/organizations/acme-corp/settings
When you create a new page for your product, place the route file at src/routes/_app/organizations/$slug/your-page.tsx and define the route like this:
export const Route = createFileRoute('/_app/organizations/$slug/your-page')({
  component: YourPage,
  loader: async ({ context }) => {
    await context.queryClient.ensureQueryData(
      yourQueryOptions(context.params.slug)
    )
  },
})
The $slug param is always available from context.params.slug inside the loader, and from Route.useParams() inside the component.

Creating organizations

Each user account can belong to up to 5 organizations and each organization can have up to 100 members. Users can create a new organization from the organizations list page or through the in-app dialog.
1

Open the organizations list

Navigate to /organizations. You’ll see all organizations you belong to, with your role badge on each card.
2

Create a new organization

Click Create new (or the dashed empty-state card if you have none). Enter a name — the slug is derived automatically.
3

Land in the new workspace

After creation, you are redirected straight to /organizations/$slug/dashboard as the Owner of the new organization.

Switching between organizations

You can belong to multiple organizations at the same time. Switch between them by navigating back to /organizations and clicking a different workspace card, or use the organization switcher in the sidebar.
Switching organizations does not sign you out. Your session persists across workspaces — only the active organization context changes.
When a user switches organizations, RefactKit updates the activeOrganizationId on their session server-side. Any server functions that read organization-scoped data use this context to return the correct records.

Conceptual overview

User account
  ├── Member of: Acme Corp  (role: owner)   → /organizations/acme-corp/
  ├── Member of: Beta Labs  (role: admin)   → /organizations/beta-labs/
  └── Member of: Side Proj  (role: member)  → /organizations/side-proj/

Each organization
  ├── members      (organizationId FK)
  ├── invitations  (organizationId FK)
  └── your tables  (organizationId FK)  ← always add this
Each organization is a fully isolated silo. A user can only access an organization’s data if the member table has a row connecting their userId to that organizationId. The server validates this on every request before any UI renders.