Using Yii 2's Gii to Generate CRUD Code for a Database Table
Generate working CRUD screens for a Yii 2 database table with Gii: safe configuration, the two-step generator workflow, what the code does, and the limits to plan around.
29 Jun 2026, 21:46 UTC

If you have a database table and need working list/view/create/update/delete screens in a Yii 2 application, the fastest supported route is Gii, the code generator bundled with the framework. Point it at a table, preview the files, and it writes a model, a search model, a controller, and views that follow Yii conventions. The whole cycle takes a few minutes, and the output is ordinary PHP you then edit like any other code.
This article assumes Yii 2.0.x with the yiisoft/yii2-gii package installed (it ships with both the basic and advanced project templates) and a working database connection component.
Enabling Gii safely
Gii is a module. You enable it in the application configuration, typically inside the YII_ENV_DEV guard so it never loads in production. In the basic template, that block lives in config/web.php:
if (YII_ENV_DEV) {
$config['modules']['gii'] = [
'class' => 'yii\gii\Module',
'allowedIPs' => ['127.0.0.1', '::1', '192.168.0.*'],
];
}The allowedIPs property is the critical line. Gii can read your schema and write files into your project, so anyone who reaches its URL effectively has code-execution-level access to your application. Restrict it to localhost or your office network, and never deploy a config where Gii is active without that filter. Some teams additionally put the whole block behind an environment variable check so it cannot be enabled by accident.
After saving, open http://your-host/index.php?r=gii in a browser. If you see a 403, your IP is not in allowedIPs; if you see a 404, the module is not registered or the dev guard evaluated false.
Generating CRUD for one table
Suppose you have a post table with columns like id, title, body, status, and created_at. The usual workflow is two steps.
Step 1: Model Generator. Enter the table name (post) and the model class name (Post, namespace app\models). Click Preview, then Generate. This produces an ActiveRecord class with validation rules inferred from column types and nullability, attribute labels derived from column names, and table name mapping that respects any tablePrefix on your DB connection.
Step 2: CRUD Generator. Fill in:
- Model Class:
app\models\Post - Search Model Class:
app\models\PostSearch - Controller Class:
app\controllers\PostController
Preview shows every file it will write — the controller plus views (index, view, create, update, _form, _search) under views/post/. Files that already exist are flagged, and you can diff or skip them. Generate, then visit index.php?r=post.
What the generated code actually does
The controller is thin by design. Each action delegates to the model or the search model:
public function actionIndex()
{
$searchModel = new PostSearch();
$dataProvider = $searchModel->search(Yii::$app->request->queryParams);
return $this->render('index', [
'searchModel' => $searchModel,
'dataProvider' => $dataProvider,
]);
}The search model builds an ActiveDataProvider with filtering conditions from query parameters, which feeds a GridView in the index view. The form view uses ActiveForm, and the detail view uses DetailView. Create and update share the same _form partial, and delete requires a POST request via VerbFilter, so a plain link cannot destroy a record.
Because the output is plain convention-following code, the right mental model is: Gii gives you a starting point, not a finished feature. You own the code from the moment it is written.
Limits and common mistakes
Relations are only sketched. If post has a foreign key to user, Gii generates a getUser() relation method, but the form will render a plain text input for user_id. Replacing that with a dropdown of users, handling belongs-to/has-many saving, and building nested forms is manual work.
Validation is schema-level only. Rules come from column types: integers get integer, NOT NULL columns get required. Business rules — unique slugs, status transitions, ownership checks — do not exist until you write them. Treat generated rules() as a floor, not a specification.
No authorization. The generated controller has no access control beyond the delete verb filter. Anyone reaching the URL can edit anything. Add an AccessControl behavior before exposing the screens:
public function behaviors()
{
return [
'access' => [
'class' => \yii\filters\AccessControl::class,
'rules' => [
['allow' => true, 'roles' => ['@']],
],
],
'verbs' => [
'class' => \yii\filters\VerbFilter::class,
'actions' => ['delete' => ['POST']],
],
];
}Regenerating overwrites edits. If you regenerate a file after customizing it, Gii will happily replace your changes (it does show a diff on overwrite, which helps). Generate early, then edit; do not use regeneration as an update mechanism.
Leaving Gii open in production is the most damaging mistake. Audit deployed configs for the gii module key, and confirm YII_ENV is not dev on production servers.
Verifying the result
Check three things. First, the generated files exist in the expected namespaces: models/Post.php, models/PostSearch.php, controllers/PostController.php, and the views/post/ directory. Second, exercise every action manually: load the index, open a record, create one with deliberately invalid input (validation errors should render), update it, and delete it. Third, if the project has tests, run them — generated code occasionally mismatches a table whose schema drifted from what Gii introspected, and a failing test catches that faster than clicking around.
If the index page renders with pagination and sorting working against your real table, the scaffolding is sound and you can move on to the part Gii cannot do: your actual business logic.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.