Skills DirectorySkills Directory
SkillsLearnSecurityCategoriesDocsCommunityBlog
Sign InSubmit Skill
Skills Directory

Security-tested agent skills for Claude, coding agents, and AI workflows.

Directory

  • Browse Skills
  • All Skills A–Z
  • Claude Skills
  • Claude Code Skills
  • Agent Skills
  • Categories
  • Submit a Skill

Learn

  • Learn Hub
  • Install Claude Skills
  • Write SKILL.md
  • Skills vs MCP
  • Directories Compared

Security

  • Security
  • Methodology
  • Secure Claude Skills
  • Security Badges

Company

  • About
  • Community
  • Blog
  • API Docs
  • Advertise

2026 Skills Directory. All rights reserved.

Back to skills

Perfex Customfields

ASecurity

Use whenever the user is reading, writing, installing, or debugging Perfex CRM custom fields — `tblcustomfields` (definitions), `tblcustomfieldsvalues` (values keyed by `relid`), field types (`input`, `textarea`, `number`, `select`, `multiselect`, `checkbox`, `date_picker`, `date_picker_time`, `link`, `colorpicker`), `fieldto` values (`contacts`, `customers`, `leads`, `invoice`, `estimate`, `contracts`, `tasks`, `tickets`, etc.), `only_admin` visibility, `show_on_client_portal`, `bs_column`, ...

3 stars
0 votes
0 copies
0 views
Added 9/19/2026
ai-agentsgophpsqldebuggingapidatabase

Works with

cliapi

Security Analysis

A100/100

Scanned 9/19/2026

Install to Claude Code

$npx -y skills add yasserstudio/perfex-crm-skills --skill perfex-customfields --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Perfex Customfields?

Add the live security badge to your README — it updates automatically with every re-scan.

Security grade badge for Perfex Customfields
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/yasserstudio-perfex-customfields/badge)](https://www.skillsdirectory.com/skills/yasserstudio-perfex-customfields)

More formats (shields.io, HTML) on the badges page.

Download Zip
Files
SKILL.md
---
name: perfex-customfields
description: Use whenever the user is reading, writing, installing, or debugging Perfex CRM custom fields — `tblcustomfields` (definitions), `tblcustomfieldsvalues` (values keyed by `relid`), field types (`input`, `textarea`, `number`, `select`, `multiselect`, `checkbox`, `date_picker`, `date_picker_time`, `link`, `colorpicker`), `fieldto` values (`contacts`, `customers`, `leads`, `invoice`, `estimate`, `contracts`, `tasks`, `tickets`, etc.), `only_admin` visibility, `show_on_client_portal`, `bs_column`, the intentionally-misspelled `disalow_client_to_edit` column, `render_custom_fields()`, `get_custom_field_value()`, or `handle_custom_fields_post()`. Also trigger when the user says "my custom field isn't showing in the client portal", "I added a custom field in code but it doesn't appear", "custom field value not saving", "only_admin isn't respected", or "Perfex custom field types". Preserves the `disalow_client_to_edit` typo that Perfex core queries by exact name.
license: MIT
metadata:
  author: yasserstudio
  version: "1.5.0"
---

# Perfex Custom Fields

You are a Perfex CRM custom-fields specialist. Your job is to read, write, and install custom fields against `tblcustomfields` and `tblcustomfieldsvalues` without tripping over Perfex's quirks — the misspelled `disalow_client_to_edit` column, `only_admin` visibility, `bs_column` Bootstrap sizing, and module-prefixed slug conventions.

Custom fields are Perfex's extensibility mechanism for adding user-defined fields to contacts, clients, leads, invoices, tickets, and most core entities. Two tables: `tblcustomfields` (definitions) and `tblcustomfieldsvalues` (values keyed by `relid`).

## Schema gotchas (critical)

### `only_admin` — NOT `only_admin_area`

The column is `only_admin`. Some older docs and Stack Overflow answers refer to `only_admin_area` — that's wrong. Don't alias, don't "fix".

### `disalow_client_to_edit` — the typo is canonical

Yes, it's misspelled (missing 'l' after 'disa'). **Preserve it.** Perfex core queries this exact column name. If you rename it, core breaks. If you write an abstraction over it, leave the DB column alone and only alias in PHP.

### Full definition-row shape

When inserting a custom field programmatically:

```php
$CI->db->insert(db_prefix() . 'customfields', [
    'fieldto'               => 'contacts',           // company | customers | contacts | leads | invoice | estimate | credit_note | proposal | contracts | expenses | projects | tasks | tickets | staff | items
    'name'                  => 'Account Tier',
    'slug'                  => 'contacts_account_tier',   // unique per fieldto; VARCHAR(150)
    'required'              => 0,
    'type'                  => 'input',              // input | textarea | number | select | multiselect | checkbox | date_picker | date_picker_time | link | colorpicker  (no 'date', 'datetime' or 'file')
    'options'               => '',                   // COMMA-separated for select/multiselect/checkbox; '' otherwise
    'display_inline'        => 0,
    'field_order'           => 0,
    'active'                => 1,
    'show_on_pdf'           => 0,                    // only honoured for fieldto in the model's $pdf_fields list
    'show_on_ticket_form'   => 0,                    // tickets only: show on the public ticket form
    'only_admin'            => 0,                    // 1 = hidden unless is_admin()
    'show_on_table'         => 0,                    // extra column in the admin datatable
    'show_on_client_portal' => 1,
    'disalow_client_to_edit'=> 0,                    // ← preserve the typo
    'bs_column'             => 12,                   // 12 | 6 | 4 | 3 — Bootstrap grid width (INT)
    'default_value'         => '',                   // 2.8.3+ (migration 283)
]);
```

That is the **complete** column list as of 3.4 (18 columns including `id`). There is no `show_on_picker`, `has_permission_view` or `permission_view` — inserting them throws `Unknown column`. Per-staff visibility of custom fields doesn't exist in core; agents borrow those names from other CRMs.

### Module-owned custom fields — convention

Prefix your module's slugs with the module name and the entity:

```
onboarding_passport_number      → contacts.onboarding_passport_number
mymodule_plan_type              → customers.mymodule_plan_type
```

This prevents slug collisions with core and other modules.

## Reading values

Values live in `tblcustomfieldsvalues` keyed by **`(fieldid, relid, fieldto)`** — yes, `fieldto` is duplicated onto the values row, and core filters on it. `relid` is the ID of the parent record (contact ID, invoice ID, etc.).

Use the core helper — it accepts a **slug or a numeric ID** in the second argument:

```php
// helper signature: get_custom_field_value($rel_id, $field_id_or_slug, $field_to, $format = true)
$plan = get_custom_field_value($contact_id, 'mymodule_plan', 'contacts');
$raw  = get_custom_field_value($invoice_id, 'invoice_due_note', 'invoice', false); // skip date formatting
```

Returns `''` (empty string, not `null`) when there's no value. With `$format = true` (default), `date_picker` / `date_picker_time` values come back through `_d()` / `_dt()` in the user's display format — pass `false` when you need the raw `Y-m-d` for comparisons.

Don't hand-roll a JOIN for this; the helper already does it and applies the `fieldto` filter you'd forget.

## Writing values

For values coming from a form, use the core helper that Perfex's own controllers use:

```php
// $_POST['custom_fields'] is ['contacts' => [<field_id> => value, ...]]
handle_custom_fields_post($contact_id, $this->input->post('custom_fields'));
```

It applies the type-specific normalisation (`to_sql_date()` for pickers, `nl2br()` for textareas, `implode(', ', …)` — comma **and space** — for checkbox/multiselect, so stored values look like `A, B`) and upserts. If you're setting a value programmatically by slug, write the same row shape core does — **including `fieldto`**, or `get_custom_field_value()` and the admin UI will never find it:

```php
public function set_custom_field_value($fieldto, $relid, $slug, $value) {
    $field = $this->db->select('id')
        ->where(['fieldto' => $fieldto, 'slug' => $slug])
        ->get(db_prefix() . 'customfields')->row();
    if (!$field) return false;

    $key = ['fieldid' => $field->id, 'relid' => $relid, 'fieldto' => $fieldto];

    if ($this->db->where($key)->count_all_results(db_prefix() . 'customfieldsvalues') > 0) {
        $this->db->where($key)->update(db_prefix() . 'customfieldsvalues', ['value' => $value]);
    } else {
        $this->db->insert(db_prefix() . 'customfieldsvalues', $key + ['value' => $value]);
    }
    return true;
}
```

Item custom fields are the exception: values for predefined items (Sales → Items) are stored with `fieldto = 'items_pr'`, not `'items'`.

## Field types — what the `type` column means

| type               | `options` format   | Storage in value column |
|--------------------|--------------------|-------------------------|
| `input`            | —                  | plain string |
| `textarea`         | —                  | string, `nl2br()` applied on save |
| `number`           | —                  | numeric string |
| `select`           | `Opt1,Opt2,…`      | the selected string |
| `multiselect`      | `Opt1,Opt2,…`      | `A, B` — comma+space joined |
| `checkbox`         | `Opt1,Opt2,…`      | `A, B` — comma+space joined |
| `date_picker`      | —                  | `YYYY-MM-DD` |
| `date_picker_time` | —                  | `YYYY-MM-DD HH:MM:SS` |
| `link`             | —                  | URL string |
| `colorpicker`      | —                  | hex string `#rrggbb` |

`options` is **comma-separated** — not JSON, not newline-separated. Core does `explode(',', $options)` and `trim()`s each entry, so an option value can't contain a comma. There is no `date`, `datetime`, or `file` type; agents invent those from other CRMs.

## Bootstrap column width (`bs_column`)

Controls visual width in the admin/client form. Allowed values: `12`, `6`, `4`, `3` (INT column, default 12). Sets the Bootstrap 3 grid class `col-md-N`; `0`/empty falls back to 12 at render time.

## Programmatically installing fields in a module

In your `install.php`:

```php
$fields = [
    [
        'fieldto' => 'contacts',
        'slug'    => 'mymodule_plan',
        'name'    => 'Plan',
        'type'    => 'select',
        'options' => 'Basic,Pro,Enterprise',   // comma-separated
        'bs_column' => 6,
    ],
    // ...
];

foreach ($fields as $f) {
    $exists = $CI->db->where(['fieldto' => $f['fieldto'], 'slug' => $f['slug']])
        ->get(db_prefix() . 'customfields')->num_rows();
    if ($exists) continue;

    $CI->db->insert(db_prefix() . 'customfields', array_merge([
        'required' => 0, 'active' => 1, 'only_admin' => 0,
        'disalow_client_to_edit' => 0, 'show_on_client_portal' => 1,
        'display_inline' => 0, 'field_order' => 0, 'show_on_pdf' => 0,
        'show_on_ticket_form' => 0, 'show_on_table' => 0,
        'default_value' => '', 'bs_column' => 12, 'options' => '',
    ], $f));
}
```

## Rendering in a custom view

Perfex ships `render_custom_fields($belongs_to, $rel_id = false, $where = [], $items_cf_params = [])`. The **third argument is a `$where` array** passed straight to `$db->where()` on `tblcustomfields` — it is not an options bag.

```php
<!-- admin view: everything active for this entity -->
<?= render_custom_fields('contacts', $contact_id); ?>

<!-- client-area view: what core's own theme views do -->
<?= render_custom_fields('contacts', $contact_id, ['show_on_client_portal' => 1]); ?>
```

What the helper enforces on its own, and what it doesn't:
- `only_admin = 1` fields are skipped when `is_admin()` is false — automatic.
- `disalow_client_to_edit = 1` renders the input `disabled` when `is_client_logged_in()` — automatic.
- `show_on_client_portal` is **not** checked by the helper. If you omit `['show_on_client_portal' => 1]` in a client-area view, portal-hidden fields render. Core's `contact.php` / `company_profile.php` pass it explicitly; copy that.
- Wrap the output in a `<form>` whose POST is handled by `handle_custom_fields_post()` — the inputs are named `custom_fields[<fieldto>][<id>]`.

`get_custom_fields($field_to, $where = [], $exclude_only_admin = false)` gives you the same filtered definition list when you need to render by hand (e.g. a table column per field).

## Required item custom fields (Perfex 3.3.0+)

As of Perfex 3.3.0, **required item custom fields on select inputs are now enforced** server-side. Previously, marking an item custom field as `required` only triggered client-side validation (which could be bypassed). Now:

- If `fieldto = 'items'` and `required = 1` and `type = 'select'`, Perfex validates on save
- Empty select values are rejected with a validation error
- This applies to invoice items, estimate items, and proposal items

If your module programmatically creates invoice items, ensure you populate all required item custom fields or the save will fail silently in older code paths that don't check for validation errors.

## Don't assume core columns haven't drifted

Older Perfex installs may lack later columns (`show_on_client_portal` arrived in 1.0.7, `only_admin` in 1.1.8, `bs_column` in 1.2.8, `default_value` in 2.8.3). Before writing migration or install code that references them, run:

```sql
SHOW COLUMNS FROM `tblcustomfields`;
```

If your code will run on older installs, wrap inserts with defensive column inclusion (only set a column if `$CI->db->field_exists()` says so).

## Related skills

- **`perfex-database`** — `tblcustomfields` schema (`only_admin`, `disalow_client_to_edit`) and why you can't "fix" the typo.
- **`perfex-module-dev`** — programmatically installing fields in a module's `install.php`.
- **`perfex-core-apis`** — `_l()` for localized field labels when rendering.

## Upstream docs

- Perfex custom fields: https://help.perfexcrm.com/custom-fields/

---

*Verified against Perfex CRM 3.4.0 core source on 2026-09-15 with `scripts/verify-against-core.sh`. Version-specific notes in the text are from official changelogs.*

Attribution

yasserstudioyasserstudio
View sourceMore from yasserstudio →
SSkills DirectorySkills Directory

Ship a skill? Prove it's safe.

Free 120-pattern security scan, letter grade, and an embeddable README badge.

Submit a skill

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments (0)

No comments yet. Be the first to comment!

SSkills DirectorySkills Directory

Ship a skill? Prove it's safe.

Free 120-pattern security scan, letter grade, and an embeddable README badge.

Submit a skill

Related Skills

Caveman

Ultra-compressed communication mode. Cuts token usage ~75% by speaking like caveman while keeping full technical accuracy. Supports intensity levels: lite, full (default), ultra, wenyan-lite, wenyan-full, wenyan-ultra. Use when user says "caveman mode", "talk like caveman", "use caveman", "less tokens", "be brief", or invokes /caveman. Also auto-triggers when token efficiency is requested.

1023331 votes

Hyperplan

Adversarial multi-agent planning skill. Self-orchestrates 5 hostile category members (unspecified-low, unspecified-high, deep, ultrabrain, artistry) via team-mode for ruthless cross-critique debate, distills only the defensible insights, then MANDATORILY hands the distilled insight bundle to the `plan` agent for executable plan formalization. Use when planning needs maximum rigor and surfacing of weak assumptions, blind spots, and over-engineering. Triggers: 'hyperplan', 'hpp', '/hyperplan', ...

686011 votes

Mcp Code Execution

Routes multi-tool workflows through MCP servers for large datasets and pipelines. Use when Bash tool overhead is limiting throughput on data-heavy tasks.

3331 votes

catchup

Recovers prior coding-agent session context by running `catchup <agent> --since-compact`, which extracts a clean summary of a previous Codex, Claude Code, Antigravity, OpenCode, or Pi Agent session. Use when the user says "catch up", "what did the last session do", "get me up to speed", "I switched agents", or asks to recover/summarize a previous session before continuing. Do NOT use for the current conversation, git history, or any non-agent log.

611 votes

math-skill

A comprehensive mathematical reasoning skill for AI assistants — handles arithmetic to research-level problems with rigorous step-by-step reasoning, systematic verification, and transparent uncertainty handling

381 votes
View all in ai-agents →