npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@alsocoder/apna-crud

v0.1.1

Published

Auto CRUD pages using ApnaTable, ApnaForm, ApnaModal, and ApnaToast.

Readme

@alsocoder/apna-crud

Auto CRUD pages built on ApnaTable, ApnaForm, ApnaModal, and ApnaToast. Drop a single <ApnaCrud /> into your admin panel (e.g. @alsocoder/apna-panel) for full list/create/edit/delete flows.


Install

npm install @alsocoder/apna-crud

For formMode="page", also install react-router-dom.

ApnaCrud bundles table, form, modal, and toast styles — one CSS import:

import "@alsocoder/apna-crud/styles.css"

Optional (for type: "media" fields):

npm install @alsocoder/apna-media-library

Quick start

REST API

<ApnaCrud
  name="users"
  title="Users"
  endpoint="/api/users"
  baseUrl={import.meta.env.VITE_API_URL}
  columns={[
    { id: "name", header: "Name", accessorKey: "name" },
    { id: "email", header: "Email", accessorKey: "email" },
  ]}
  formFields={[
    { type: "input", name: "name", label: "Name", props: { required: true } },
    { type: "input", name: "email", label: "Email", props: { format: "email", required: true } },
  ]}
  permissions={{
    list: "users.view",
    create: "users.create",
    update: "users.update",
    delete: "users.delete",
  }}
  features={["bulk"]}
/>

Custom functions

<ApnaCrud
  name="users"
  title="Users"
  list={listUsers}
  create={createUser}
  update={updateUser}
  delete={deleteUser}
  get={getUser}
  columns={[...]}
  formFields={[...]}
/>

Inside ApnaPanel

import { ApnaPanelRoute } from "@alsocoder/apna-panel-react-router"
import { ApnaCrud } from "@alsocoder/apna-crud"
import "@alsocoder/apna-crud/styles.css"

<Route
  path="/users/*"
  element={
    <ApnaPanelRoute permissions="users.view" fallback={<ApnaPanelForbidden />}>
      <ApnaCrud
        formMode="page"
        basePath="/users"
        name="users"
        title="Users"
        {...usersResource}
      />
    </ApnaPanelRoute>
  }
/>

ApnaCrud props — full reference

| Prop | Type | Default | Description | |------|------|---------|-------------| | name | string | required | Resource name (used for routes, data-resource attr) | | title | string | — | Page/table title | | endpoint | string \| ApnaCrudEndpoints | — | REST endpoint(s) | | baseUrl | string | — | API base URL (with endpoint) | | list | fn | — | (params) => { items, total } | | create | fn | — | (data) => record | | update | fn | — | (id, data) => record | | delete | fn | — | (id) => void | | get | fn | — | (id) => record — required for page mode direct URL | | columns | ApnaTableColumn[] | auto | Table columns | | formFields | ApnaFormFieldConfig[] | — | Create/edit form fields | | formColumns | ApnaFormColumns | — | Responsive form grid { default: 1, md: 2, lg: 3 } | | formProps | ApnaCrudFormProps | — | Forwarded to ApnaFormBuilder (see below) | | formMode | "modal" \| "drawer" \| "page" | "modal" | How create/edit forms open | | basePath | string | /${name} | Base path for page mode | | routes | { create?, edit? } | — | Custom route segments for page mode. Default create segment is create; /add is also accepted as an alias unless routes.create is customized. | | overlaySize | "sm" \| "md" \| "lg" \| "xl" | "md" | Modal/drawer size | | permissions | ApnaCrudPermissions | — | list, create, update, delete permission keys | | features | string[] | — | "bulk", "export", "import", "archive", "duplicate", "softDelete" | | idKey | string | "id" | Record id field name | | getRowId | fn | — | Custom row id extractor | | transformCreate | fn | — | Transform payload before create | | transformUpdate | fn | — | Transform payload before update | | onSuccess | fn | — | (action, record?) => void | | onError | fn | — | (error, action) => void | | className | string | — | Root wrapper class | | emptyMessage | string | — | Empty table message |


Form modes

Modal (default)

Create/edit opens in a centered modal.

<ApnaCrud
  formMode="modal"
  overlaySize="md"
  name="products"
  title="Products"
  formFields={[...]}
  {...api}
/>

Drawer

Create/edit opens in a right-side drawer.

<ApnaCrud
  formMode="drawer"
  overlaySize="lg"
  name="orders"
  title="Orders"
  formFields={[...]}
  {...api}
/>

Page (route-based)

Full-page forms at separate routes. Mount under a splat route:

// router
<Route path="/users/*" element={<UsersCrudPage />} />

function UsersCrudPage() {
  return (
    <ApnaCrud
      formMode="page"
      basePath="/users"
      name="users"
      title="Users"
      get={getUser}       // required for direct /users/:id/edit access
      list={listUsers}
      create={createUser}
      update={updateUser}
      delete={deleteUser}
      columns={[...]}
      formFields={[...]}
    />
  )
}

Auto-created routes:

| URL | Screen | |-----|--------| | /users | Table list | | /users/create | Create form | | /users/add | Create form (alias when default create segment is used) | | /users/:id/edit | Edit form |

Custom route segments:

routes={{ create: "new", edit: ":id" }}
// → /users/new and /users/:id/edit only (no /add alias when create is customized)

Data sources

REST with createRestCrud

import { ApnaCrud, createRestCrud } from "@alsocoder/apna-crud"

const usersApi = createRestCrud<User>(import.meta.env.VITE_API_URL, "/api/users")

<ApnaCrud
  name="users"
  {...usersApi}
  columns={[...]}
  formFields={[...]}
/>

Custom endpoints object

endpoint={{
  list: "/api/users",
  create: "/api/users",
  update: "/api/users/:id",
  delete: "/api/users/:id",
  get: "/api/users/:id",
}}
baseUrl="https://api.example.com"

defineCrudResource (reusable config)

import { defineCrudResource } from "@alsocoder/apna-crud"

export const usersResource = defineCrudResource({
  name: "users",
  title: "Users",
  columns: [
    { id: "name", header: "Name", accessorKey: "name" },
    { id: "email", header: "Email", accessorKey: "email" },
  ],
  formFields: [
    { type: "input", name: "name", label: "Name" },
    { type: "input", name: "email", label: "Email", props: { format: "email" } },
  ],
  formColumns: { default: 1, md: 2 },
  permissions: {
    list: "users.view",
    create: "users.create",
    update: "users.update",
    delete: "users.delete",
  },
  features: ["bulk"],
  ...usersApi,
})

// usage
<ApnaCrud {...usersResource} formMode="modal" />

Forms (formFields + formProps)

Forms are rendered by ApnaFormBuilder from @alsocoder/apna-form. ApnaCrud handles table, modal/drawer/page shell, and API calls.

Basic fields

formFields={[
  { type: "input", name: "title", label: "Title", colSpan: { md: 8 }, props: { required: true } },
  { type: "select", name: "status", label: "Status", colSpan: { md: 4 }, props: { options, placeholder: "Select" } },
  { type: "textarea", name: "notes", label: "Notes", props: { rows: 4 } },
  { type: "date", name: "dueDate", label: "Due date" },
  { type: "checkbox", name: "active", label: "Active" },
]}
formColumns={{ default: 1, md: 2, lg: 3 }}

Advanced fields

formFields={[
  { type: "editor", name: "body", label: "Content" },
  { type: "editor", name: "html", label: "HTML", props: { enableHtmlMode: true } },
  { type: "upload", name: "resume", label: "Resume", props: { accept: ".pdf", maxSize: 5 * 1024 * 1024 } },
  { type: "media", name: "cover", label: "Cover", props: { folder: "blog/covers" } },
]}

formProps (forwarded to ApnaFormBuilder)

Everything from ApnaFormBuilderProps except config, columns, children, onSubmit, className, classNames:

import type { ApnaUploadFn } from "@alsocoder/apna-upload"
import { z } from "zod"

const schema = z.object({
  name: z.string().min(1),
  email: z.string().email(),
})

const uploadFile: ApnaUploadFn = async (file, { signal, onProgress }) => {
  const formData = new FormData()
  formData.append("file", file)
  onProgress(30)
  const res = await fetch("/api/upload", { method: "POST", body: formData, signal })
  onProgress(100)
  return res.json()
}

<ApnaCrud
  formFields={[...]}
  formProps={{
    schema,                              // Zod / validation schema
    mode: "onBlur",                       // validation mode
    upload: uploadFile,                   // global upload handler
    transformUploadResult: (r) => r.path, // normalize upload response
    defaultUploadProps: {
      accept: "image/*",
      maxSize: 5 * 1024 * 1024,
    },
  }}
  {...resource}
/>

Media picker setup

For type: "media" fields, wrap your app with ApnaMediaProvider once at the app level:

npm install @alsocoder/apna-media-library
import { ApnaMediaProvider } from "@alsocoder/apna-media-library"
import "@alsocoder/apna-media-library/styles.css"

<ApnaMediaProvider adapter={mediaAdapter}>
  <ApnaPanelRouterProvider config={config}>
    <ApnaPanelShell>
      <Routes>...</Routes>
    </ApnaPanelShell>
  </ApnaPanelRouterProvider>
</ApnaMediaProvider>

Pass formProps.upload for type: "upload" fields. See ApnaForm README for full field docs.


Permissions

permissions={{
  list: "users.view",      // hide table / page
  create: "users.create",  // hide create button
  update: "users.update",  // hide edit action
  delete: "users.delete",  // hide delete action
}}

Wire to your panel RBAC — ApnaCrud checks permission strings you provide. Use the same keys as @alsocoder/apna-panel rolePermissions.


Features

features={["bulk", "export", "import", "archive", "duplicate", "softDelete"]}

| Feature | Description | |---------|-------------| | bulk | Multi-select + bulk delete | | export | Export table data | | import | Import data | | archive | Archive records | | duplicate | Duplicate row | | softDelete | Soft delete instead of hard delete |


Callbacks & transforms

<ApnaCrud
  transformCreate={(data) => ({ ...data, createdBy: currentUser.id })}
  transformUpdate={(data) => ({ ...data, updatedAt: new Date().toISOString() })}
  onSuccess={(action, record) => {
    if (action === "create") toast.success(`Created ${record?.name}`)
  }}
  onError={(error, action) => {
    toast.error(`${action} failed: ${error.message}`)
  }}
  {...resource}
/>

Complete examples

1. Modal CRUD (products)

<ApnaCrud
  name="products"
  title="Products"
  formMode="modal"
  overlaySize="md"
  formColumns={{ default: 1, md: 2 }}
  columns={[
    { id: "name", header: "Product", accessorKey: "name" },
    { id: "sku", header: "SKU", accessorKey: "sku" },
    { id: "price", header: "Price", accessorKey: "price" },
    { id: "status", header: "Status", accessorKey: "status" },
  ]}
  formFields={[
    { type: "input", name: "name", label: "Product name", colSpan: { md: 6 }, props: { required: true } },
    { type: "input", name: "sku", label: "SKU", colSpan: { md: 6 }, props: { required: true } },
    { type: "input", name: "price", label: "Price", colSpan: { md: 4 }, props: { format: "number" } },
    { type: "select", name: "status", label: "Status", colSpan: { md: 4 }, props: { options: statusOptions } },
  ]}
  permissions={{ list: "products.view", create: "products.create", update: "products.update", delete: "products.delete" }}
  features={["bulk"]}
  list={listProducts}
  create={createProduct}
  update={updateProduct}
  delete={deleteProduct}
/>

2. Drawer CRUD (orders)

<ApnaCrud
  name="orders"
  title="Orders"
  formMode="drawer"
  overlaySize="lg"
  columns={[...]}
  formFields={[...]}
  {...ordersApi}
/>

3. Page CRUD with large form (projects)

<ApnaCrud
  name="projects"
  title="Projects"
  formMode="page"
  basePath="/crud/projects"
  formColumns={{ default: 1, md: 2, lg: 3 }}
  columns={[
    { id: "title", header: "Project", accessorKey: "title" },
    { id: "client", header: "Client", accessorKey: "client" },
    { id: "status", header: "Status", accessorKey: "status" },
  ]}
  formFields={[
    { type: "input", name: "title", label: "Title", colSpan: { md: 8 } },
    { type: "media", name: "coverImage", label: "Cover", colSpan: { md: 6 }, props: { folder: "covers" } },
    { type: "upload", name: "contract", label: "Contract", props: { accept: ".pdf" } },
    { type: "editor", name: "brief", label: "Brief" },
    { type: "editor", name: "contentHtml", label: "Scope", props: { enableHtmlMode: true } },
  ]}
  formProps={{
    upload: uploadFile,
    defaultUploadProps: { accept: "image/*,.pdf", maxSize: 10 * 1024 * 1024 },
  }}
  get={getProject}
  list={listProjects}
  create={createProject}
  update={updateProject}
  delete={deleteProject}
  features={["bulk"]}
/>

4. REST API one-liner

import { ApnaCrud, createRestCrud, defineCrudResource } from "@alsocoder/apna-crud"

const api = createRestCrud<Product>("https://api.example.com", "/products")

export const productsResource = defineCrudResource({
  name: "products",
  title: "Products",
  columns: [...],
  formFields: [...],
  ...api,
})

<ApnaCrud {...productsResource} baseUrl="https://api.example.com" endpoint="/products" />

Hooks & utilities

import { useApnaCrud, createRestCrud, fetchJson, defineCrudResource } from "@alsocoder/apna-crud"
import { ApnaCrudPageRoutes, getApnaCrudBasePath } from "@alsocoder/apna-crud"

| Export | Description | |--------|-------------| | useApnaCrud(config, baseUrl?) | Headless CRUD state (advanced) | | createRestCrud(baseUrl, endpoint) | REST list/create/update/delete/get helpers | | fetchJson(url, options?) | JSON fetch with error handling | | defineCrudResource(config) | Type-safe resource config builder | | ApnaCrudPageRoutes | Low-level page mode router (used internally) | | getApnaCrudBasePath(config) | Resolve base path for page mode |


ApnaPanel + ApnaCrud full wiring

// panel config menu
menu: defineMenu([
  { id: "dashboard", label: "Dashboard", href: "/" },
  { id: "products", label: "Products", href: "/crud/products", permission: "products.view" },
  { id: "users", label: "Users", href: "/crud/users", permission: "users.view" },
])

// routes
<Route path="/" element={<ApnaPanelRoute permissions="dashboard.view"><ApnaPanelHomePage /></ApnaPanelRoute>} />
<Route path="/crud/products" element={<ApnaPanelRoute permissions="products.view"><ProductsPage /></ApnaPanelRoute>} />
<Route path="/crud/users/*" element={<ApnaPanelRoute permissions="users.view"><UsersPage /></ApnaPanelRoute>} />

// products page — modal mode
function ProductsPage() {
  return <ApnaCrud formMode="modal" {...productsResource} formProps={sharedFormProps} />
}

// users page — page mode (needs splat route)
function UsersPage() {
  return <ApnaCrud formMode="page" basePath="/crud/users" {...usersResource} formProps={sharedFormProps} />
}

Shared formProps (upload, schema) at page or app level — set once, pass to every <ApnaCrud />.


Playground demos

See ApnaPanel/playground for live examples:

| Demo | formMode | Features | |------|------------|----------| | Products | modal | Basic fields, bulk delete | | Orders | drawer | Drawer overlay | | Users | page | Route-based forms | | Projects | page | Editor, upload, media, 18+ fields |

cd ApnaPanel && npm run dev:playground

License

MIT